Repository: ethercreative/simplemap Branch: v5 Commit: 0f99bcd14cfd Files: 95 Total size: 634.2 KB Directory structure: gitextract_o1ic_4j1/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── craft-3-issue.md │ │ └── craft-4-issue.md │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── docs/ │ ├── .docs.json │ ├── geolocation/ │ │ ├── get.md │ │ └── redirect.md │ ├── getting-started/ │ │ ├── config.md │ │ ├── installation.md │ │ └── usage.md │ ├── how-to/ │ │ ├── graphql.md │ │ └── search.md │ ├── index.md │ └── rendering/ │ ├── embed.md │ └── static.md ├── resources/ │ ├── .editorconfig │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── src/ │ │ ├── App.vue │ │ ├── common/ │ │ │ └── Geo.js │ │ ├── components/ │ │ │ ├── Address.vue │ │ │ ├── Fragment.vue │ │ │ ├── Input.vue │ │ │ ├── Label.vue │ │ │ ├── Map.vue │ │ │ └── Search.vue │ │ ├── enums/ │ │ │ ├── GeoService.js │ │ │ └── MapTiles.js │ │ ├── filters/ │ │ │ └── craft.js │ │ ├── helpers/ │ │ │ ├── createW3WGrid.js │ │ │ ├── debounce.js │ │ │ └── waitForGlobal.js │ │ ├── main.js │ │ └── models/ │ │ ├── Parts.js │ │ └── PartsLegacy.js │ └── vue.config.js └── src/ ├── SimpleMap.php ├── acfadapters/ │ └── GoogleMap.php ├── config.php ├── controllers/ │ ├── SettingsController.php │ └── StaticController.php ├── enums/ │ ├── GeoService.php │ └── MapTiles.php ├── fields/ │ └── MapField.php ├── integrations/ │ ├── feedme/ │ │ └── FeedMeMaps.php │ └── graphql/ │ ├── MapPartsType.php │ └── MapType.php ├── jobs/ │ └── MaxMindDBDownloadJob.php ├── migrations/ │ ├── Install.php │ ├── m190226_143809_craft3_upgrade.php │ ├── m190325_130533_repair_map_elements.php │ ├── m190712_104805_new_data_format.php │ └── m190723_105637_fix_map_field_column_type.php ├── models/ │ ├── BaseLocation.php │ ├── EmbedOptions.php │ ├── Map.php │ ├── Marker.php │ ├── Parts.php │ ├── PartsLegacy.php │ ├── Point.php │ ├── Settings.php │ ├── StaticOptions.php │ └── UserLocation.php ├── records/ │ └── Map.php ├── resources/ │ └── OpenSans_LICENSE.txt ├── services/ │ ├── EmbedService.php │ ├── GeoLocationService.php │ ├── GeoService.php │ ├── MapService.php │ ├── StaticService.php │ └── What3WordsService.php ├── templates/ │ ├── _feedme-mapping.twig │ ├── field-settings.twig │ └── settings.twig ├── translations/ │ └── en/ │ └── simplemap.php ├── utilities/ │ └── StaticMap.php └── web/ ├── Variable.php └── assets/ ├── MapAsset.php └── map/ ├── css/ │ ├── app.css │ └── chunk-vendors.css ├── index.html └── js/ ├── app.js └── chunk-vendors.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = tab indent_size = 4 charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.{yml, json}] indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false ================================================ FILE: .gitattributes ================================================ README.md export-ignore CHANGELOG.md export-ignore /resources export-ignore .gitignore export-ignore .gitattributes export-ignore /.idea export-ignore .editorconfig export-ignore /docs export-ignore ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Code of Conduct Don't be a twat. ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing ### New Features You can request new features by submitting an issue with `[FR]` prefix in the title. ### Bugs If you find a bug, submit an issue and I promise I will get around to it. Eventually. Make sure you search around to see if the same (or similar) issue exists before submitting a new one. Know how to fix a bug and have too much spare time on your hands? Submit a Pull Request! Check out the code conventions below before hand. ## Code Conventions In addition to [Crafts Guidelines](https://github.com/craftcms/docs/blob/v3/en/coding-guidelines.md): - Use tabs not spaces - Try to keep within the 80 characters line length - If function arguments or array values can't fit on one line, break each value onto it's own line - Comment as much as possible #### JavaScript In addition to the above: - Write valid ES6 - Use single quotes `'` Don't follow my example, try to stick to the above guidelines! ================================================ FILE: .github/FUNDING.yml ================================================ github: [tam] ================================================ FILE: .github/ISSUE_TEMPLATE/craft-3-issue.md ================================================ --- name: Craft 3 Issue about: An issue with the Craft 3 version of the plugin title: '' labels: Craft 3 assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/craft-4-issue.md ================================================ --- name: Craft 4 Issue about: An issue with the Craft 4 version of the plugin title: '' labels: Craft 4 assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ### Description ### Steps to reproduce 1. 2. ### Additional info - Craft version: - Maps version: - PHP version: - Database driver & version: - Other Plugins: ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ Fixes # . Changes proposed in this pull request: - - - ================================================ FILE: .gitignore ================================================ ### OSX .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk vendor node_modules hot mix-manifest.json /.idea ================================================ FILE: CHANGELOG.md ================================================ ## 5.0.4 - 2024-11-11 ### Added - Add support for WordPress ACF -> Craft import (via @brandonkelly) ## 5.0.3 - 2024-09-16 ### Fixed - Fixed location filter affecting unrelated queries (Fixes #405) ## 5.0.2 - 2024-09-10 ### Fixed - Fixes location search (Fixes #394, #393, #392) ## 5.0.1 - 2024-06-27 [CRITICAL] ### Security - Removed Polyfill.io (https://sansec.io/research/polyfill-supply-chain-attack) ## 5.0.0 - 2024-04-04 ### Fixed - Fix referred on Mapbox geo requests (Fixes #338, via @maxdmyers) - Fix type errors in front-end usage (Fixes #379, via @samhibberd) - Fix error when normalizing invalid location (Fixes #368, #380, via @Decyphr) - Fix intermittent issues w/ Google Maps API loading (Fixes #294, via @davidwebca) - Allow nullable zoom value (Fixes #381) - Pass site language to embedded Google map (Fixes #373) - Support casting map to string (Fixes #362) - Remove reference to MaxMind Lite from docs (Fixes #358) ## 5.0.0-rc1 - 2024-03-01 ### Changed - Add support for Craft 5 ## 4.0.4 - 2023-06-26 ### Fixed - Update settings autosuggest input (Fixes #374) ## 4.0.3 - 2022-07-11 ### Fixed - Fixed $id embed issue (Fixes #353) - Allow all Parts to be null as well as an empty string (Fixes #349) - Fix issue placing marker along coastline when using Mapbox (Fixes #322) ### Changed - Removed WikiMedia tiles, falling back to OpenStreetMap (Fixes #307) ## 4.0.2 - 2022-06-08 ### Fixed - Misc fixes via @davidwebca & @jamesedmonston ## 4.0.1 - 2022-05-18 ### Fixed - Fix Geo Location Token only allowing string (Fixes #343) ## 4.0.0 - 2022-05-10 ### Fixed - Mostly functional, only slightly buggy, Craft 4 Support ## 3.9.3 - 2022-01-17 ### Fixed - Fix Mapbox Forbidden issue (Fixes #218, via @Saboteur777) ## 3.9.2 - 2021-08-26 ### Changed - Change default map tileset to Carto Voyager ### Fixed - Fix some geolocation services conversion failing when the given location is not a valid address (Fixes #322) - Fix error when upgrading due to missing `type` field (via @cornernote) ## 3.9.1 - 2021-04-30 ### Added - Add Guzzle 7 support (via @dwheeldo) ## 3.9.0.2 - 2020-12-01 ### Improved - Improved GQL docs ### Fixed - Fix GQL Coords type missing `lng` ## 3.9.0.1 - 2020-11-27 ### Fixed - Removed distance field from GQL mutation input ## 3.9.0 - 2020-11-27 > {warning} **BREAKING**: This release changes how GraphQL querying works for the map field. You should now pass the search query as an input rather than a JSON string. ### Added - Added GraphQL support for query filtering and mutations ### Improved - Address inputs will span full width when map is disabled (Fixes #282) ## 3.8.5 - 2020-10-08 ### Added - Add `centerFallback` option to map embeds (Fixes #263) ### Changed - Prefix name of `Map` graphql elements ### Fixed - Reduce search radius to 0 if location is not valid (Fixes #277) - Fix mapbox geocode error when country is not valid ISO code (Fixes #276) ### Removed - Remove MaxMind Lite option ## 3.8.4.1 - 2020-07-03 ### Fixed - Fix url encode issue for markers in static map images ## 3.8.4 - 2020-06-12 ### Changed - Allow zoom override on Google/Mapbox embeds - Allow style override on Mapbox embeds (Fixes #256) ### Fixed - Cast embed center coordinates to floats - Upgraded Mapbox to use new Static Tiles API ## 3.8.3 - 2020-04-09 ### Added - Added JSON support when filtering by a map field. Very useful for Crafts current GQL implementation (Fixes #248) ### Fixed - Markers coodinates are now cast to floats (via [@Sekonda](https://github.com/Sekonda)) - Exclude empty map fields when sorting by distance (Fixes #245) ## 3.8.2 - 2020-03-04 ### Fixed - Fix issue where elements failed to save when searching for a location via Google or Here (Fixes #242) - Fix what3words not updating when searching for a location ## 3.8.1 - 2020-02-27 ### Added - Add env support to settings (Closes #241) ### Improved - Improve settings page appearance during load ## 3.8.0 - 2020-02-25 ### Added - Add What3Words support (Closes #236) ### Fixed - Fix issue when trying to render a static map without markers (Fixes #225) ## 3.7.7 - 2020-01-17 ### Fixes - Fix error when attempts to populate missing data return null (Fixes #233) ## 3.7.6 - 2019-12-18 ### Fixed - Fix issue with getting distance when lat/lng was stored as string (Fixes #230) - Fix issue when upgrading maps for fields that no longer exist (Fixes #227) ## 3.7.5 - 2019-12-04 ### Added - Add config option to disable missing field data population (Fixes #226) - Add Current Location button to Map (Closes #219) ## 3.7.4 - 2019-12-03 ### Improved - 🔥 Improved location searching and distance sorting by up to 1800%! ⚡️ ## 3.7.3 - 2019-11-29 ### Fixed - Fix issue with Mapbox parts lookup when address property is missing - Fix issue with count when doing a location search ## 3.7.2 - 2019-11-19 ### Changed - Embed map width and height can now be set to `null` (Fixes #221) ### Fixed - Fix map embed / static options not converting center string correctly - Fix multiple leaflet maps not rendering correctly - Fix map not being draggable on mobile (Fixes #220) - Fix mini-map going off screen on small laptops (Fixes #222) ## 3.7.1 - 2019-10-24 ### Added - Add support for `:empty:` and `:notempty:` (Fixes #214) ### Fixed - Fix `embed` and `imgSrcSet` not setting options correctly when outputting from a map field (Fixes #215) ## 3.7.0 - 2019-10-15 ### Added - Add docs - Add Craft GraphQL support - Add Pro edition - Add static map image support - Add new map field size options - Add IP based user location lookup - Add ability to redirect to a specific site based off user location - Add `coordinate` query argument to CraftQL (Closes #205) - Add "mini" size for a tiny field footprint (Closes #203) - Add `address()` method to map value for easy address formatting - Add galactic address parts ### Changed - 🍆 New, sexier UI! 💦 - Mapbox, Apple Maps, and Here are now only available in Maps Pro - Now requires Craft 3.2.1 or newer ### Improved - Remove Vue from JS bundle to reduce file size - Removed fly animation when updating map location for snappier UI (Closes #202) ### Fixed - Fix map not showing when other Vue based plugins interfere (Fixes #196) - Fix issue when migrating from an older version of Maps (Fixes #195) - Fix project config migration issue (Fixes #207) - Fix issues upgrading Maps from Craft 2 to 3 (via [@roelvanhintum](https://github.com/roelvanhintum)) ## 3.6.4.3 - 2019-08-30 ### Fixed - Fix issue when trying to save map field on initial draft entry ## 3.6.4.2 - 2019-08-01 ### Changed - Don't update project config unnecessarily during migration (Closes #194) ### Fixed - Fix migration error when upgrading from 3.3.4 or lower (Fixes #192) ## 3.6.4.1 - 2019-07-30 ### Fixed - Fix error when populating legacy parts server-side from lat/lng - Fix error when logging invalid legacy part ## 3.6.4 - 2019-07-30 ### Fixed - Remove errant debug code causing migration to run every request (Fixes #190) - Fix migration trying to change a column type to a table (Fixes #189, #188) ## 3.6.3 - 2019-07-25 ### Added - Add min / max zoom settings to map field (Closes #186) ### Fixed - Fix migration from Craft 2 (Fixes #153) - Fix issue when column already exists during migration (Fixes #187) ## 3.6.2.2 - 2019-07-24 ### Fixed - Fix migration issue when matrix / super table blocks don't have any fields (Fixes #184) ## 3.6.2.1 - 2019-07-23 ### Fixed - Fix migration issue when no matrix or SuperTable blocks exist (Fixes #182) - Fix issue with Google trying to set legacy parts that aren’t supported (Fixes #183) - Fix getting top-level map value part if no parts exist (Fixes #181) ## 3.6.2 - 2019-07-23 ### Added - Add `postal_code_suffix` to `PartsLegacy` (Fixes #179) ### Fixed - Fix migration error when upgrading from 3.4.x to 3.6.x (Fixes #178) - Fix project config content column type being string instead of text (Fixes #180) ## 3.6.1 - 2019-07-19 ### Added - Add support for getting parts without having to go via the `parts` property. (i.e. `myMap.parts.number` can be simply `myMap.number`). This _doesn't_ work for the `address` part, which is already in use and returns the full address as a string (alternatively, use the `streetAddress` alias). (Closes #154) - Add `streetAddress` alias of `address` to Parts. ### Changed - `PartsLegacy` will be used when Google is the chosen Geo service, giving access to additional Google specific parts (Fixes #167) ### Fixed - Fix error when normalizing value without an element (Fixes #174) - Fix JS error when using two different API keys for Google maps services (Fixes #165) - Fix parts being lost when moving from new to legacy (any other geo service to google) - Fix issue with Mapbox geo service when country was unrestricted - Fix JS issues when using Apple or Google Maps in an element edit HUD (Fixes #175) ## 3.6.0 - 2019-07-12 > {warning} This update changes how map data is stored, moving away from an element type. This means if you are eager loading the a map field, you'll want to remove the `with` from your query and `[0]` when outputting the map (if you have it). We also **strongly** recommend taking a backup before updating. > {tip} If you get a `Column not found` error when upgrading, try running `./craft migrate/all`. ### Changed - Reformat data structure to remove map element type and need for eager loading ### Fixed - Fix missing postcode warning (Fixes #169) - Fix map save DB issue in Craft 3.2 (Fixes #170) - Fix map not retrieving saved values in Craft 3.2 (Fixes #171) - Fix DB error on duplicate import via FeedMe (Fixes #168) - Fix maps not propagating across sites (Fixes #141) ## 3.5.2 - 2019-06-20 ### Improved - FeedMe can now import the individual map parts ## 3.5.1 - 2019-06-20 ### Added - Maps can now populate address and lat/lng data based off only a postcode ## 3.5.0 - 2019-06-13 ### Added - Added FeedMe support! ### Fixed - Fixed results being duplicated when searching by location when an entry has multiple map fields within the search catchment. - Account for missing Craft 2 API keys - Fix HERE search not working when no country restriction was set ### Changed - Add default zoom to map element - Update preferred country instructions to be clearer - Support rendering a map field without a value - Use field handle as table alias suffix, instead of random bytes ## 3.4.11 - 2019-04-05 ### Fixed - Map records are no longer double saved when upgrading to from Craft 2 to 3 ## 3.4.10 - 2019-04-04 ### Fixed - Map records are no longer double saved when upgrading to >3.4.x ## 3.4.9 - 2019-04-01 ### Added - Added option to show lat / lng fields ### Fixed - Fixed map not validating correctly - Fixed wrong map value being shown on element index with multiple sites - Fixed missing table prefix in map element query - Fixed migration issue when upgrading due to duplicate element IDs ### Improved - Scrolling to zoom disabled on map - Clearing the map will no longer store the default data ## 3.4.8 - 2019-03-27 ### Fixed - Fix error when migrating a field from Craft 2 when `countryRestriction` isn't set - Location search excludes elements that have been soft-deleted - Fixed issue restoring trashed elements that have a map field - Map field elements are trashed and deleted correctly - Fixed syntax issue on PHP <7.1.0 - Fixed error during repair migration when element doesn't exist ## 3.4.7 - 2019-03-25 ### Fixed - Fixed JS error when clearing field - Fixed missing parts when using Google maps for geo-coding ### Improved - Clear button now translatable ## 3.4.6 - 2019-03-25 ### Added - Added "Clear" button - Always show full address field even if address block is hidden ### Fixed - The really shitty element stuff. Is good now. I think. ## 3.4.5 - 2019-03-25 ### Fixed - Fixed maps failing to get value after save ### Changed - Using Google Maps geo service will result in legacy parts always being used, meaning you can access all available address components. ## 3.4.4 - 2019-03-22 ### Fixed - Fixed some issues when upgrading from older versions of Maps. We recommend upgrading from 3.3.4 or lower directly to this release or later. ## 3.4.3 - 2019-03-20 ### Changed - You can now pass a map to the location query (fixes #99) ### Fixed - Fixed issue when `cp-field-inspect` plugin is installed (fixes #127) - Fixed `elementId cannot be null` error on saving new entries with map fields (fixes #126) ## 3.4.2 - 2019-03-20 ### Fixed - Fixed issue setting old field settings after upgrade. ## 3.4.1 - 2019-03-20 ### Fixed - Fixed an issue where the map field class broke after upgrading. ## 3.4.0 - 2019-03-20 > {warning} This is a major update, we strongly recommend taking a database backup before updating! ### Changed - SimpleMap is now Maps! We've re-written the plugin from the ground-up while keeping it backwards compatible (even back to Craft 2!) - Maps is now powered by Vue! - New icon yo ### Added - OpenStreetMap Support and map tiles - Mapbox Support and map tiles - Apple MapKit Map Tiles - Here Maps Support and map tiles - Wikimedia Map Tiles - Carto Map Tiles - Address inputs for manually settings address parts data. ### Improved - We've normalized the map "Parts", so you'll always know what data you have available. - CraftQL support: you can now query and mutate Maps fields via Graph! - Field Customization: It's now possible to hide the location search, map, and address inputs. ### Fixed - Maps are now multi-site aware and can be translated. ### Removed - Removed lat/lng inputs from field - Removed restrict by type - Removed boundary restriction ## 3.3.4 - 2018-09-05 ### Fixed - Fixed a bug where SimpleMap would not validate required fields. (via @samhibberd) ## 3.3.3 - 2018-03-13 ### Fixed - Fixed a bug where SimpleMap would cause the `ResaveElements` job to error when triggered via console. ## 3.3.2 - 2018-03-05 ### Added - Added docs for using a config file to configure the plugin. ### Fixed - Fixed JOIN alias issue when using the Element API plugin (via @idontmessabout) ## 3.3.1 - 2018-01-30 ### Fixed - Fixed JS bug on settings page ## 3.3.0 - 2018-01-30 ### Fixed - Added a fix for those annoying `Call to a member function getMap() on null` bugs ### Improved - Map height no longer jumps when page loads - Vastly improved the map fields settings UI/UX - No more nasty text fields! - Map height and position is now set by resizing and moving a map - Auto-complete search bounds can now be drawn directly onto a map - Radio buttons are now drop-downs ### Changed - Now using the plugins `afterInstall` function instead of the plugin after install event - The "Hide Lat/Lng" option is now true by default ## 3.2.0 - 2018-01-25 ### Fixed - Fixed bug where pagination would error when querying via a map field. #70 ### Improved - Updated CraftQL support (via @markhuot) - Removed webonyx/graphql-php dependency #71 - Improved address and lat/lng input sizing on smaller screens and in a HUD #73 - Updated Mapbox example to use latest API #74 ## 3.1.3 - 2017-12-18 ### Fixed - Map fields no longer cause global sets to fail to save! ## 3.1.2 - 2017-12-18 ### Fixed - Fixed settings not translating for non-English languages - Fixed boundary settings fields not accepting decimals ## 3.1.1 - 2017-11-30 ### Fixed - Fixed bug where maps were failing to save. ## 3.1.0 - 2017-11-30 ### Added - [CraftQL](https://github.com/markhuot/craftql) support! - Added `craft.simpleMap.getLatLngFromAddress($addressString[, $country])`. ### Improved - The maps `parts` now contains all available options from [here](https://developers.google.com/maps/documentation/geocoding/intro#Types) (including the `_small` variants). Any options without values are returned as empty strings. ## 3.0.4 - 2017-11-28 ### Added - Added ability to restrict location search by country ### Changed - New icon! ## 3.0.3 - 2017-11-08 ### Added - It's now possible to save the map field with only an address! Useful for populating the field from the front-end. (Requires the Geocoding API). ### Improved - The address and lat/lng are now validated. ## 3.0.2 - 2017-11-03 ### Fixed - Fixed a bug where location searches would error if `orderBy` was not defined ## 3.0.1 - 2017-11-03 ### Fixed - Fixed maps not rendering ## 3.0.0 - 2017-11-03 ### Changed - Initial Craft 3 Release ================================================ FILE: LICENSE ================================================ Copyright © Ether Creative Permission is hereby granted to any person obtaining a copy of this software (the “Software”) to use, copy, modify, merge, publish and/or distribute copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 1. **Don’t plagiarize.** The above copyright notice and this license shall be included in all copies or substantial portions of the Software. 2. **Don’t use the same license on more than one project.** Each licensed copy of the Software shall be actively installed in no more than one production environment at a time. 3. **Don’t mess with the licensing features.** Software features related to licensing shall not be altered or circumvented in any way, including (but not limited to) license validation, payment prompts, feature restrictions, and update eligibility. 4. **Pay up.** Payment shall be made immediately upon receipt of any notice, prompt, reminder, or other message indicating that a payment is owed. 5. **Follow the law.** All use of the Software shall not violate any applicable law or regulation, nor infringe the rights of any other person or entity. Failure to comply with the foregoing conditions will automatically and immediately result in termination of the permission granted hereby. This license does not include any right to receive updates to the Software or technical support. Licensees bear all risk related to the quality and performance of the Software and any modifications made or obtained to it, including liability for actual and consequential harm, such as loss or corruption of data, and any necessary service, repair, or correction. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, INCLUDING SPECIAL, INCIDENTAL AND CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ![Maps](./resources/imgs/map-banner.jpg) # Maps A beautifully simple, yet deceptively powerful, Map field that works out of the box with no setup or API tokens needed! Configure the map field to only show the features you want; hide the address, show latitude / longitude, or hide the map entirely. Choose from 4 geocoding services, 3 geolocation services, and 24 map tilesets! Maps offers full multi-site support, compatibility with Matrix, [SuperTable](https://verbb.io/craft-plugins/super-table/features), and [CraftQL](https://plugins.craftcms.com/craftql), and the ability to search by location and sort by distance. ![Maps Field](./resources/imgs/normal.png) ![Searching](./resources/imgs/normal-searching.png) ## Mini Map Maps offers a mini map field that fits perfectly in a Super Table without taking up a lot of space! ![Mini](./resources/imgs/mini.png) ## Map Tiles and Geo Maps supports the following map tiles:
Wikimedia [Wikimedia](https://foundation.wikimedia.org/wiki/Maps_Terms_of_Use) | | | --- | --- | --- ![Wikimedia](./resources/imgs/tilesets/wikimedia.png) | | Wikimedia | |
OpenStreetMap [OpenStreetMap](https://www.openstreetmap.org) | | | --- | --- | --- ![OpenStreetMap](./resources/imgs/tilesets/openstreetmap.png) | | OpenStreetMap | |
Carto (Voyager, Positron, Dark Matter) [Carto](https://carto.com/location-data-services/basemaps/) | | | --- | --- | --- ![Carto Voyager](./resources/imgs/tilesets/carto-rastertiles-voyager.png) | ![Carto Positron](./resources/imgs/tilesets/carto-light_all.png) | ![Carto Dark Matter](./resources/imgs/tilesets/carto-dark_all.png) Voyager | Positron | Dark Matter
Mapbox (Outdoors, Streets, Dark, Light) [Mapbox](https://www.mapbox.com) | | | --- | --- | --- ![Mapbox Outdoors](./resources/imgs/tilesets/mapbox-outdoors.png) | ![Mapbox Streets](./resources/imgs/tilesets/mapbox-streets.png) | ![Mapbox Dark](./resources/imgs/tilesets/mapbox-dark.png) Outdoors | Streets | Dark ![Mapbox Light](./resources/imgs/tilesets/mapbox-light.png) | | Light | |
Google Maps (Roadmap, Terrain, Hybrid) [Google Maps](https://www.google.com/maps) | | | --- | --- | --- ![Google Roadmap](./resources/imgs/tilesets/google-roadmap.png) | ![Google Terrain](./resources/imgs/tilesets/google-terrain.png) | ![Google Hybrid](./resources/imgs/tilesets/google-hybrid.png) Roadmap | Terrain | Hybrid
Apple MapKit (Standard, Muted, Satellite, Hybrid) [Apple MapKit](https://developer.apple.com/maps/mapkitjs/) | | | --- | --- | --- ![MapKit Standard](./resources/imgs/tilesets/mapkit-standard.png) | ![MapKit Muted](./resources/imgs/tilesets/mapkit-muted.png) | ![MapKit Satellite](./resources/imgs/tilesets/mapkit-satellite.png) Standard | Muted | Satellite ![MapKit Hybrid](./resources/imgs/tilesets/mapkit-hybrid.png) | | Hybrid | |
Here (Day, Day Grey, Day Transit, Reduced, Pedestrian, Terrain, Satellite, Hybrid) [Here](https://www.here.com/) | | | --- | --- | --- ![Here Day](./resources/imgs/tilesets/here-normal-day.png) | ![Here Day Grey](./resources/imgs/tilesets/here-normal-day-grey.png) | ![Here Day Transit](./resources/imgs/tilesets/here-normal-day-transit.png) Day | Day Grey | Day Transit ![Here Reduced](./resources/imgs/tilesets/here-reduced-day.png) | ![Here Pedestrian](./resources/imgs/tilesets/here-pedestrian-day.png) | ![Here Terrain](./resources/imgs/tilesets/here-terrain-day.png) Reduced | Pedestrian | Terrain ![Here Satellite](./resources/imgs/tilesets/here-satellite-day.png) | ![Here Hybrid](./resources/imgs/tilesets/here-hybrid-day.png) | Satellite | Hybrid |
And these geocoding services: - [Nominatim (OpenStreetMap)](https://nominatim.openstreetmap.org/) - [Mapbox](https://www.mapbox.com/) - [Google Maps](https://www.google.com/maps) - [Here](https://www.here.com/) And these geolocation services: - [ipstack](https://ipstack.com/) - [MaxMind Lite](https://dev.maxmind.com/geoip/geoip2/geolite2/) - [MaxMind](https://maxmind.com/) ## Documentation For full documentation visit the **[Maps Ether Docs](https://docs.ethercreative.co.uk/maps)**. ================================================ FILE: composer.json ================================================ { "name": "ether/simplemap", "description": "A beautifully simple Map field type for Craft CMS", "type": "craft-plugin", "license": "proprietary", "minimum-stability": "dev", "require": { "craftcms/cms": "^5.0.0", "mapkit/jwt": "^1.1.2", "geoip2/geoip2": "~2.0", "guzzlehttp/guzzle": "^6.3.3|^7.2.0", "what3words/w3w-php-wrapper": "3.*", "ext-openssl": "*", "ext-json": "*", "ext-zlib": "*", "php": "^8.2" }, "require-dev": { "craftcms/wp-import": "dev-main" }, "autoload": { "psr-4": { "ether\\simplemap\\": "src/" } }, "support": { "email": "help@ethercreative.co.uk", "docs": "https://docs.ethercreative.co.uk/maps", "source": "https://github.com/ethercreative/simplemap", "issues": "https://github.com/ethercreative/simplemap/issues" }, "extra": { "handle": "simplemap", "name": "Maps", "developer": "Ether Creative", "developerUrl": "https://ethercreative.co.uk", "class": "ether\\simplemap\\SimpleMap", "schemaVersion": "3.4.2" }, "config": { "allow-plugins": { "yiisoft/yii2-composer": true, "craftcms/plugin-installer": true } } } ================================================ FILE: docs/.docs.json ================================================ { "nav": { "index": "Introduction", "getting-started": { "_label": "Getting Started", "installation": "Installation", "config": "Configuration", "usage": "Usage" }, "how-to": { "_label": "How-to Guides", "search": "Search by Location", "graphql": "Querying in GraphQL" }, "rendering": { "_label": "Rendering", "static": "Static", "embed": "Embed" }, "geolocation": { "_label": "Geo-location", "get": "Get User Location", "redirect": "User Location Redirecting" } } } ================================================ FILE: docs/geolocation/get.md ================================================ --- title: Get User Location --- # Get User Location Getting the current users location based of their IP address is easy using the handy Craft Twig method: ```twig {% set userLocation = craft.maps.getUserLocation([ip]) %} ``` - **`ip`** is an optional parameter that expects a valid and not private or reserved IPv4 or IPv6. If null the function will use the IP address from the user of the current request. This function returns a [`UserLocation`](#User-Location) which is very similar to the location returned by a Map field, with a few added bonuses. ## User Location The user location has the following properties: - **`ip`** The IP address of the user (that was used to lookup the location). - **`lat`** The latitude of the users location. - **`lng`** The longitude of the users location. - **`address`** The full address (see [Address](../getting-started/usage.md#address)). - **`parts`** The separate parts of the address (see [Parts](../getting-started/usage.md#parts)). - **`countryCode`** The ISO country code of the user locations country. - **`isEU`** Will be true if the user is in an EU country. ### Distance You can get the distance between the user and a given location using this method on the User Location: ```twig {{ userLocation.distance({ lat: 51.272154, lng: 0.514951 }, 'miles') }} ``` The method accepts two parameters: - **`to`** A lat/lng keyed array, address string, or a Map or User location. - **`unit`** An optional parameter specifying which unit to use for the measurement. Either `mi` (miles) or `km` (kilometers). _Defaults to `km`._ It will return a float of the distance between the two locations in the unit specified (or `km` if no unit is specified). ================================================ FILE: docs/geolocation/redirect.md ================================================ --- title: User Location Redirecting --- # User Location Redirecting With Maps it is now possible to redirect the user to a specific site based off their physical location. You can do this by setting the [`geoLocationRedirectMap`](../getting-started/config.md#geolocationredirectmap) in the [`simplemap.php`](../getting-started/config.md) config file. The value of `geoLocationRedirectMap` should be a keyed array. The key of each item should be the handle for the site you want to redirect to. The value of each item should be an array of properties to match against ([explained below](#location-matching)), or a string containing an asterisk `*` which will act as a catch-all. ## Location Matching Location matching is performed by looking at each key/value pair in an array and checking if they all match the users location. The keys should match the available properties in the [User Location](./get.md#user-location). The values should be an exact match for the contents of the User Location. The priority is top-down, first come first serve. This means that the first site that matches the users location will be the one that is used. See the example below for a visual explanation. ### Example ```php [ 'uk' => [ 'countryCode' => ['uk', 'ie'] ], 'eu' => [ 'isEU' => true ], 'southern' => [ 'lat' => function ($lat) { return $lat <= 0; } ], 'global' => '*', ], ]; ``` The first site, `uk`, is checking to see if the `countryCode` of the users location matches either `'uk'` or `'ie'` because we want to bundle the Irish in with the English. I'm sure they won't mind. If it does match, the user will be redirected to the current page on the UK site. The second site, `eu`, checks to see if the user is in the EU and will redirect to the `eu` site if they are. `southern` is using an anonymous function, or lambda, to check if the users latitude is on or below the equator. You can use lambdas for any of the properties. The first and only argument will be the value of that property on the users location. All lambdas must return a boolean value. The final site, `global`, uses an asterisk as a catch-all. This means that if none of the previous rules match this one will be used. You should always use the wildcard last since any subsequent rules will be ignored. ================================================ FILE: docs/getting-started/config.md ================================================ --- title: Configuration --- # Configuration There are two ways to configure **Maps**, via the Craft CP or using a config file (for advanced configuration). ### Services & Tokens Below is a list of the various services supported by Maps and links on how to get their tokens. #### Map Tiles & Geocoding ##### OpenStreetMap / Nominatim No token required ##### Wikimedia No token required ##### Carto No token required ##### [Google](https://cloud.google.com/maps-platform/#get-started) You will need to enable the **Maps JavaScript API** and **Places API** for if using Google for the map tiles, and the **Places API** and **Geocoding API** if you are using it for the Geo service. ##### [Mapbox](https://docs.mapbox.com/help/how-mapbox-works/access-tokens/) You can use the same key for both map tiles and geo service, no configuration needed! ##### [Apple MapKit](https://developer.apple.com/documentation/mapkitjs/setting_up_mapkit_js) We currently only support Apple MapKit for map tiles only. ##### [Here](https://developer.here.com/) You can use the same key for both map tiles and geo service, no configuration needed! #### Geolocation ##### [ipstack](https://ipstack.com/product) ipstack offer free and paid-for versions of their API. ##### [MaxMind](https://www.maxmind.com/en/geoip2-precision-services) MaxMind offer free lookup database that must be stored locally, and a more accurate paid-for version of their API. ## CP You can get to the Maps settings in the Craft CP by navigating to "Settings" -> "Maps" in an environment where `allowAdminChanges` is set to `true`. ## Config File For advanced configuration create a `simplemap.php` file in your `config` folder. This file should return an array of Maps settings. ```php '123abc', ]; ``` ### Settings #### `mapTiles` _Default: `MapTiles::Wikimedia`_ The map tileset to use. Must be set to one of the `MapTiles` constants. ```php MapTiles::CartoVoyager, ]; ``` #### `mapToken` _Default: `''`_ The token to use with your selected map tileset. This is only required when you are using a tileset that requires a token. **Mapbox & Google Maps** For these services your token should be a string containing the token. ```php '', ]; ``` **Apple MapKit** Your token should be an array containing `privateKey`, `teamId`, `keyId`. ```php [ 'privateKey' => '', 'teamId' => '', 'keyId' => '', ], ]; ``` **Here** The token should be an array containing `appId`, `apiKey`, `appCode`. ```php [ 'appId' => '', 'apiKey' => '', 'appCode' => '', ], ]; ``` #### `geoService` _Default: `GeoService::Nominatim`_ The geocoding service to use. Must be set to one of the `GeoService` constants. ```php GeoService::GoogleMaps, ]; ``` #### `geoToken` _Default: `''`_ The token to use with your selected geocoding service. This is only required when you are using a geocoding that requires a token. **Mapbox & Google Maps** For these services your token should be a string containing the token. ```php '', ]; ``` **Here** The token should be an array containing `appId`, `appCode`. ```php [ 'appId' => '', 'appCode' => '', ], ]; ``` #### `disablePopulateMissingFieldData` _Default: `false`_ Will disable the automatic population of missing field data. This can be useful in preventing API spam when importing lots of map data. #### `geoLocationService` _Default: `GeoLocationService::None`_ The geolocation service to use. Must be set to one of the `GeoLocationService` constants. ```php GeoLocationService::MaxMind, ]; ``` #### `geoLocationToken` _Default: `''`_ The token to use with your selected geolocation service. This is only required when you are using a geolocation that requires a token. **ipstack** For this services your token should be a string containing the token. ```php '', ]; ``` **MaxMind** The token should be an array containing `accountId`, `licenseKey`. ```php [ 'accountId' => '', 'licenseKey' => '', ], ]; ``` #### `geoLocationCacheDuration` _Default: `'P2M'`_ A string (a [duration interval](https://en.wikipedia.org/wiki/ISO_8601#Durations)) or int (in seconds) of how long we should cache IP lookups. #### `geoLocationAutoRedirect` _Default: `false`_ Will automatically redirect the user according to `geoLocationRedirectMap` when set to true. #### `geoLocationRedirectMap` _Default: `[]`_ This dictates what site the user is redirected to based off their IPs location. It should be a key value array where key is the handle of the site to redirect, and value is a key value array of user location properties and their required matches or an string to catch all. For more details on how to setup your geolocation redirects have a look at the [Geolocation / Redirect](../geolocation/redirect.md) docs. ```php [ 'uk' => [ 'country' => 'uk' ], 'eu' => [ 'isEU' => true ], 'global' => '*', ], ]; ``` ================================================ FILE: docs/getting-started/installation.md ================================================ --- title: Installation --- # Installation ## Plugin Store You can install **Maps** from the [Craft Plugin Store](https://plugins.craftcms.com/simplemap) inside your Craft admin! Simply search for "Maps" and click the icon. ## Composer To install via command-line execute the following from inside your projects root directory: ```bash composer require ether/simplemap ./craft install/plugin simplemap ``` ================================================ FILE: docs/getting-started/usage.md ================================================ --- title: Usage --- # Usage ## Creating a Map field You can create a Map field in the same way you would create any other field in Craft. Simple go to "Settings" -> "Fields" and click the "New Field" button. Fill out the fields required and select "Map" from the "Field Type" select. You can now configure the initial state of the map and how it appears to the user. Use the Map to select the initial location of the map and the fields below the map to customize the layout and any restrictions. ## Displaying the Map When accessing the Map field in twig you will have access to the following properties: - **`lat`** The latitude of the selected maps location - **`lng`** The longitude of the selected maps location - **`zoom`** The zoom level of the map - **`address`** The full address (see [Address](#address)) - **`parts`** The separate parts of the address (see [Parts](#parts)) - **`distance`** The distance the location is from your search (only populated when [Searching](#searching)) ### Address The `address` comes in two flavours. The first is as a string, and will output the Full Address as it appears in the Map field. ```twig {{ myMapField.address }} ``` The second is as a function, `address([exclude = [] [, glue = '
']])`, which will allow you to output the address in a more formatted way. Both arguments are optional. - **`exclude`** expects an array of [Parts](#parts) that you don't want to appear when outputting the address. _Defaults to `[]`_. - **`glue`** is the string that joins the parts together. _Defaults to `'
'`. ```twig {{ myMapField.address(['country'], ', ') }} ``` The example above is outputting the address as a comma-separated string, excluding the country. ### Parts The parts contains the, well, parts that make up the address. - **`number`** The name or number of the location - **`address`** The street address of the location (not the full address) - **`city`** The city in which the location is situated - **`postcode`** The postal or zip code of the location - **`county`** The county of the location - **`state`** The state or region of the location - **`country`** The locations country   - **`planet`** The planet of the location - **`system`** The system containing the planet of the location - **`arm`** The galactic arm of the location - **`galaxy`** The galaxy the arm is attached to - **`group`** The group the locations galaxy belongs to - **`cluster`** The galaxy cluster containing the group - **`supercluster`** The supercluster the galaxy belongs to ```twig {{ myMapField.parts.city }} ``` You can also access the parts directly from the map with the exception of the `address` part, which can be accessed via `streetAddress`. ```twig {{ myMapField.streetAddress }} {{ myMapField.city }} ``` ### Searching When querying elements you can filter them by proximity to a given location. To do so, simply pass an address and radius to the Map field in the [element query](https://docs.craftcms.com/v3/dev/element-queries/). - **`location`** An address string, map field, or `{ lat: 0, lng: 0 }` object to search by. - **`country`** An optional country to restrict the address string to. - **`radius`** The radius around the location to get results from. _Defaults to `50`_. - **`unit`** The distance unit to use. Can be either `mi` or `km`. _Defaults to `km`_. ```twig {% set entries = craft.entries.myMapField({ location: 'Maidstone, Kent', country: 'UK', radius: 100, unit: 'mi', }).all() %} ``` If you search using this method you will have access to the `distance` property in the resulting elements Map fields. This will return the distance that location is from the location searched for, in the unit specified when searching. You can also sort by `distance` when searching for an address. ```twig {% set entries = craft.entries.myMapField({ location: { lat: 51.272154, lng: 0.514951 }, }).orderBy('distance').all() %} ``` ```twig {% set entries = craft.entries.myMapField({ location: myOtherMapField, }).orderBy('distance desc').all() %} ``` ================================================ FILE: docs/how-to/graphql.md ================================================ --- title: Querying in GraphQL --- # Querying in GraphQL The query input can support all the parameters that you can use in regular [Searching](../getting-started/usage/#searching), with the exception that `location` only supports a string value. This means if you want to search by lat/lng you need to pass them to the `coordinate` input. ```graphql { entries ( map: { unit: Kilometres location: "Maidstone, Kent" country: "UK" radius: 10 coordinate: { lat: 51.27136675686769 lng: 0.4939985275268555 } } section: "locations" orderBy: "distance" ) { title ... on locations_locations_Entry { map { lat lng distance zoom address parts { number address city postcode county state country } } } } } ``` ================================================ FILE: docs/how-to/search.md ================================================ --- title: Search by Location --- # Search by Location Being able to search your maps by a location is one of the most important parts of using a map plugin. So here's how you do it. When building your [element query](https://docs.craftcms.com/v3/dev/element-queries/) (in either Twig or PHP) you can pass an object to the map field that will let you search by your location. This object can have the following properties: - **`location`**: An address string, map field, or `{ lat: 0, lng: 0 }` object to search by. - **`country`**: An optional country to restrict the address string to. - **`radius`**: The radius around the location to get results from. _Defaults to `50`_. - **`unit`**: The distance unit to use. Can be either `mi` (miles) or `km` (kilometres). _Defaults to `km`_. ### By Address Let's say you want to search for a location within 10 miles of a given address (this can be a full address, or just part of an address like a town or city name). In that case you would do the following: ```twig {% set entries = craft.entries.myMapField({ location: 'Maidstone, Kent', radius: 10, unit: 'mi', }).all() %} ``` Here we're saying that we want to find all locations with 10 (radius) miles (unit) of Maidstone, Kent (location). ### By Coordinates Alternatively you could have a set of coordinates that you want to search by. In that case you would pass the coordinates to the `location` parameter instead of an address string. ```twig {% set entries = craft.entries.myMapField({ location: { lat: 51.272154, lng: 0.514951 }, }).all() %} ``` By excluding the other fields we're letting them fall back to their defaults (as specified above). In this case we're searching for all locations around those given coordinates within 50 kilometres. ================================================ FILE: docs/index.md ================================================ --- title: Maps --- ![Maps](map-banner.png) # Maps A beautifully simple, yet deceptively powerful map field that works out of the box with no setup or API tokens needed! Configure the map field to show only the features you want; display latitude/longitude, hide the address or even the entire map. Choose from 4 geocoding services, 3 geolocation services, and 24 map tilesets! 'Maps' offers full multi-site support, compatibility with Matrix, [SuperTable](https://verbb.io/craft-plugins/super-table/features), [CraftQL](https://plugins.craftcms.com/craftql), and the ability to search by location and sort by distance. **[View Maps on the Plugin Store](https://plugins.craftcms.com/simplemap)** ![Maps Field](./normal.png) ![Searching](./normal-searching.png) ================================================ FILE: docs/rendering/embed.md ================================================ --- title: Embed Dynamic Maps --- # Embed Dynamic Maps With **Maps** you can quickly and easily output an interactive map using one of two templating methods. The first is via a Map field, and the second is using the global map variable. ## Options *The available options include those from the [Static Map](./static.md#options) as well as two additional options:* - **`id`** - The ID to use when creating the map (using in JavaScript and on the HTML tag). - **`options`** - An object of options that will be passed to the JavaScript, allowing you to customise the map according to the map library being used. ## From a Map field Both of the below `embed` methods will return a div tag with the given (or generated) ID, and will include the necessary JavaScript and CSS to render the map. They both accept an [options](#options) object as their only parameter. ### `mapField.embed([options])` To render a dynamic map from a Map field, use the `embed` method on the fields value. The `center` and `zoom` options will be ignored, since their values are gathered from the Map field's value. ```twig {{ myMapField.embed({ id: 'map', markers: [{}], }) }} ``` ### `craft.maps.embed([options])` ```twig {{ craft.maps.embed({ center: 'Maidstone, UK', options: { disableDefaultUI: true, draggable: false, }, }) }} ``` ## Additional attributes If you want to add additional attributes to the output div from the `embed` methods you should do so using Craft's built-in [`|attr` filter](https://docs.craftcms.com/v3/dev/filters.html#attr). ```twig {{ myMapField.embed()|attr({ class: 'map', }) }} ``` ## Libraries & Caveats ### Google Maps The Google Maps service uses Google's [Maps JavaScript library](https://developers.google.com/maps/documentation/javascript/reference/). You can view the options that you can pass to `options.options` [here](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions). **Caveats** - Google Maps doesn't support coloured markers. ### Apple Maps Apple Maps uses the [Mapkit JS library](https://developer.apple.com/documentation/mapkitjs). You can view the options [here](https://developer.apple.com/documentation/mapkitjs/mapconstructoroptions). ### Mapbox Mapbox uses [Mapbox](https://docs.mapbox.com/mapbox-gl-js/api/). You can view the options [here](https://docs.mapbox.com/mapbox-gl-js/api/#map). **Caveats** - Mapbox doesn't support marker labels. ### Here Here uses [Here](https://developer.here.com/documentation/maps/topics/overview.html). You can view the options [here](https://developer.here.com/documentation/maps/topics_api/h-map-options.html). ### Others Wikimedia, OSM, and Carto all use [Leaflet JS](https://leafletjs.com/). You can view the options [here](https://leafletjs.com/reference-1.5.0.html#map-option). ================================================ FILE: docs/rendering/static.md ================================================ --- title: Static Map Images --- # Static Map Images **Maps** makes it really easy to render static map images in twig. There are two ways of rendering a static map image. First is via a Map field, second is using the global map variable. ## Options Both methods support the following options, passed as a Twig object to the method (we'll cover that later): - **`center`** - This can be an address string (i.e. "Maidstone, UK"), a lat / lng variable (i.e. `[51.272154, 0.514951]` or `{ lat: 51.272154, lng: 0.514951 }`). - **`centerFallback`** - The fallback location that will be used if the center specified above is empty. Must be lat / lng. - **`width`** - The width of the map image (see max image sizes below). - **`height`** - The height of the map image (see max image sizes below). - **`zoom`** - The zoom level of the map (must be between 0 and 18). - **`scale`** - The scale of the image (can be either 1 or 2 (retina), defaults to 1). - **`markers`** - An array of map [markers](#markers) (see below) ### Markers The `markers` option accepts an array of objects with the following parameters (all are optional): - **`location`** - This can be an address string (i.e. "Maidstone, UK"), a lat / lng variable (i.e. `[51.272154, 0.514951]` or `{ lat: 51.272154, lng: 0.514951 }`). If left blank the marker will appear at the center of the map. - **`color`** - A valid hex colour string. Will default to `#ff0000` if blank. - **`label`** - An option label, must be a single character A-Z0-9. ## From a Map field To render a map from a Map field, use the `img` or `imgSrcSet` methods on the fields value: ```twig {% set myMapField = entry.myMapField %} {{ myMapField.address }} ``` ### `mapField.img([options])` The `img` method returns the URL for the static map image. It accepts an [options](#options) object as its only parameter. Since we already have a location and zoom level from the Map field, the `center` and `zoom` options will be ignored. ```twig {{ entry.mapField.img({ width: 800, height: 600, }) }} ``` ### `mapField.imgSrcSet([options])` `imgSrcSet` is similar to `img` accept it returns a `srcset` ready string, supporting @1x and @2x resolutions. As with `img` it accepts an [options](#options) object as its only parameter. Along with the `center` and `zoom` options being ignored (as with `img`), the `scale` option is also ignored. ## Using the global `maps` variable You can turn any address into a static map using the globally available `maps` variable. ```twig ``` ### `craft.maps.img([options])` The `img` method returns the URL for the static map image. It accepts an [options](#options) object as its only parameter. ```twig {{ craft.maps.img({ center: 'Maidstone, UK', width: 800, height: 600, }) }} ``` ### `craft.maps.imgSrcSet([options])` `imgSrcSet` is similar to `img` accept it returns a `srcset` ready string, supporting @1x and @2x resolutions. As with `img` it accepts an [options](#options) object as its only parameter. The `scale` is ignored. ================================================ FILE: resources/.editorconfig ================================================ [*.{js,jsx,ts,tsx,vue}] indent_style = tab indent_size = 4 end_of_line = lf insert_final_newline = true max_line_length = 80 ================================================ FILE: resources/.gitignore ================================================ .DS_Store node_modules /dist # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw* ================================================ FILE: resources/README.md ================================================ # simplemap ## Project setup ``` yarn install ``` ### Compiles and hot-reloads for development ``` yarn run serve ``` ### Compiles and minifies for production ``` yarn run build ``` ### Run your tests ``` yarn run test ``` ### Lints and fixes files ``` yarn run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). ================================================ FILE: resources/babel.config.js ================================================ module.exports = { presets: [ '@vue/app', ], }; ================================================ FILE: resources/package.json ================================================ { "name": "simplemap", "version": "0.1.0", "private": true, "scripts": { "dev": "PORT=8080 vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "leaflet": "^1.4.0", "leaflet.gridlayer.googlemutant": "^0.8.0", "leaflet.mapkitmutant": "^0.3.0", "vue": "^2.6.10", "vue-autosuggest": "^2.0.0" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.3.0", "@vue/cli-plugin-eslint": "^3.3.0", "@vue/cli-service": "^3.3.0", "@vue/eslint-config-airbnb": "^4.0.0", "babel-eslint": "^10.0.1", "eslint": "^5.8.0", "eslint-plugin-vue": "^5.0.0", "less": "^3.0.4", "less-loader": "^4.1.0", "vue-template-compiler": "^2.5.21", "webpack-bundle-analyzer": "^3.4.1" }, "eslintConfig": { "root": true, "env": { "node": true }, "extends": [ "plugin:vue/essential", "@vue/airbnb" ], "rules": { "no-tabs": "off", "indent": "off", "padded-blocks": "off", "comma-dangle": "off", "arrow-parens": "off", "space-before-function-paren": "off", "no-underscore-dangle": "off", "no-param-reassign": "off", "nonblock-statement-body-position": "off", "func-names": "off", "prefer-template": "off", "lines-between-class-members": "off", "class-methods-use-this": "off", "curly": "off", "prefer-destructuring": "off", "no-trailing-spaces": "off", "import/extensions": "off", "max-len": "off", "comma-style": "off", "import/prefer-default-export": "off", "one-var": "off", "operator-linebreak": "off", "no-multi-spaces": "off", "no-prototype-builtins": "off", "object-curly-newline": "off", "no-plusplus": "off", "no-continue": "off" }, "parserOptions": { "parser": "babel-eslint", "ecmaFeatures": { "legacyDecorators": true } } }, "postcss": { "plugins": { "autoprefixer": {} } }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ] } ================================================ FILE: resources/src/App.vue ================================================ ================================================ FILE: resources/src/common/Geo.js ================================================ import GeoService from '../enums/GeoService'; import Parts from '../models/Parts'; import PartsLegacy from '../models/PartsLegacy'; import waitForGlobal from '../helpers/waitForGlobal'; export default class Geo { // Properties // ========================================================================= country = null; service = null; token = null; locale = null; google = { service: null, session: null }; apple = { Search: null }; // Constructor // ========================================================================= constructor ({ country, geoService: service, geoToken: token, locale }) { this.country = country ? country.toLowerCase() : null; this.service = service; this.token = token; this.locale = locale; if (service === GeoService.GoogleMaps) { waitForGlobal('google', () => this.initGoogle()); } else if (service === GeoService.AppleMapKit) { waitForGlobal('mapkit', () => this.initApple(token)); } } // Initializers // ========================================================================= initGoogle () { this.google = { service: new window.google.maps.places.AutocompleteService(), session: new window.google.maps.places.AutocompleteSessionToken(), geocoder: new window.google.maps.Geocoder(), places: new window.google.maps.places.PlacesService( document.createElement('div') ), }; } initApple (token) { window.mapkit.init({ authorizationCallback: done => done(token), }); this.apple = { Search: new window.mapkit.Search(), Geocoder: new window.mapkit.Geocoder(), Coordinate: window.mapkit.Coordinate, }; } // Actions // ========================================================================= // Actions: Search // ------------------------------------------------------------------------- /** * Run the search * * @param {string} text * @returns {Promise} */ async search (text) { if (!text || text.trim() === '') { return []; } let suggestions = []; switch (this.service) { case GeoService.Nominatim: suggestions = await this.searchNominatim(text); break; case GeoService.Mapbox: suggestions = await this.searchMapbox(text); break; case GeoService.GoogleMaps: suggestions = await this.searchGoogle(text); break; case GeoService.AppleMapKit: suggestions = await this.searchApple(text); break; case GeoService.Here: suggestions = await this.searchHere(text); break; default: throw new Error('Unknown geocoding service: ' + this.service); } return suggestions; } /** * Search using Nominatim * * @param {string} query * @returns {Promise<*>} */ async searchNominatim (query) { const params = new URLSearchParams({ q: query, format: 'jsonv2', limit: 5, addressdetails: 1, countrycodes: this.country, 'accept-language': this.locale, }).toString(); const data = await fetch( 'https://nominatim.openstreetmap.org/search?' + params ).then(res => res.json()); return data.map(result => ({ address: result.display_name, lat: result.lat, lng: result.lon, parts: new Parts({ ...result.address, type: result.type, }, GeoService.Nominatim), })); } /** * Search using Mapbox * * @param {string} query * @returns {Promise<*>} */ async searchMapbox (query) { const rawParams = { types: 'address,country,postcode,place,locality,district,neighborhood', limit: 5, access_token: this.token, language: this.locale, }; if (this.country) rawParams.country = this.country; const params = new URLSearchParams(rawParams).toString(); const data = await fetch( 'https://api.mapbox.com/geocoding/v5/mapbox.places/' + query + '.json?' + params ).then(res => res.json()); return data.features.map(result => ({ address: result.place_name, lat: result.center[1], lng: result.center[0], parts: new Parts(result, GeoService.Mapbox), })); } /** * Search using Google Places * * @param {string} query * @returns {Promise<*>} */ searchGoogle (query) { return new Promise(resolve => { this.google.service.getPlacePredictions({ input: query, sessionToken: this.google.session, componentRestrictions: { country: this.country, }, }, predictions => { if (!predictions) return resolve([]); return resolve(predictions.map(result => ({ __placeId: result.place_id, address: result.description, // See Geo::getGooglePlaceDetails() for `lat`, `lng`, and `parts` }))); }); }); } /** * Search using Apple MapKit * * @param {string} query * @return {Promise<*>} */ searchApple (query) { return new Promise(resolve => { this.apple.Search.autocomplete(query, (err, data) => { resolve(data.results.slice(0, 5).map(result => ({ address: result.displayLines.join(', '), lat: result.coordinate.latitude, lng: result.coordinate.longitude, // There's no way to get detailed address information from MapKit :( parts: new Parts(null, GeoService.AppleMapKit), }))); }); // TODO: Workout how to support preferred country }); } /** * Search using Here * * @param {string} query * @return {Promise<*>} */ async searchHere (query) { const params = new URLSearchParams({ app_id: this.token.appId, app_code: this.token.appCode, query, country: this.country ? this.country.toUpperCase() : '', maxresults: 5, language: this.locale, }).toString(); const data = await fetch( 'https://autocomplete.geocoder.api.here.com/6.2/suggest.json?' + params ).then(res => res.json()); if (!data.hasOwnProperty('suggestions')) return []; return data.suggestions.map(suggestion => ({ __placeId: suggestion.locationId, address: suggestion.label, // See Geo::getHerePlaceDetails() for `lat`, `lng`, and `parts` })); } // Actions: Reverse // ------------------------------------------------------------------------- /** * Lookup the given lat/lng using Nominatim * * @param lat * @param lng * @param oldVal * @return {Promise<{address: *, lng: *, parts: Parts, lat: *}>} */ async reverseNominatim ({ lat, lng }, oldVal) { const params = new URLSearchParams({ lat, lon: lng, format: 'jsonv2', addressdetails: 1, 'accept-language': this.locale, }).toString(); const result = await fetch( 'https://nominatim.openstreetmap.org/reverse?' + params ).then(res => res.json()); if (!result || (result.hasOwnProperty('error') && result.error)) return { address: '', lat, lng, parts: new Parts() }; return { ...oldVal, address: result.display_name, lat, lng, parts: new Parts({ ...result.address, type: result.type, }, GeoService.Nominatim), }; } /** * Lookup the given lat/lng using Mapbox * * @param lat * @param lng * @param oldVal * @return {Promise<{address: *, lng: *, parts: Parts, lat: *}>} */ async reverseMapbox ({ lat, lng }, oldVal) { const params = new URLSearchParams({ types: 'address,country,postcode,place,locality,district,neighborhood', limit: 1, access_token: this.token, language: this.locale, }).toString(); const result = await fetch( 'https://api.mapbox.com/geocoding/v5/mapbox.places/' + lng + ',' + lat + '.json?' + params ).then(res => res.json()); const feature = result.features[0]; if (!feature) return { address: '', lat, lng, parts: new Parts() }; return { ...oldVal, address: feature.place_name, lat, lng, parts: new Parts(feature, GeoService.Mapbox), }; } /** * Lookup the given lat/lng using Google Maps * * @param latLng * @param oldVal * @return {Promise} */ reverseGoogle (latLng, oldVal) { return new Promise(resolve => { this.google.geocoder.geocode({ location: latLng, }, results => { const result = results[0]; resolve({ ...oldVal, address: result.formatted_address, ...latLng, parts: new PartsLegacy( result.address_components ), }); }); }); } /** * Lookup the given lat/lng using Apple MapKit * * @param lat * @param lng * @param oldVal * @return {Promise} */ reverseApple ({ lat, lng }, oldVal) { return new Promise(resolve => { this.apple.Geocoder.reverseLookup( new this.apple.Coordinate(lat, lng), (err, data) => { const result = data.results[0]; resolve({ ...oldVal, address: result.formattedAddress, lat, lng, // There's no way to get detailed address information from MapKit :( parts: new Parts(null, GeoService.AppleMapKit), }); } ); }); } /** * Lookup the given lat/lng using Here * * @param lat * @param lng * @param oldVal * @return {Promise<{address: *, lng: *, parts: Parts, lat: *}>} */ async reverseHere ({ lat, lng }, oldVal) { const params = new URLSearchParams({ app_id: this.token.appId, app_code: this.token.appCode, mode: 'retrieveAddresses', jsonattributes: 1, limit: 1, prox: `${lat},${lng},1`, language: this.locale, }); const { response } = await fetch( 'https://reverse.geocoder.api.here.com/6.2/reversegeocode.json?' + params ).then(res => res.json()); if (response.view.length === 0) return { address: '', lat, lng, parts: new Parts() }; const { address } = response.view[0].result[0].location; return { ...oldVal, address: address.label, lat, lng, parts: new Parts(address, GeoService.Here), }; } // Helpers // ========================================================================= /** * Gets the details about the given place * * @param {string} placeId * @param {Object} item * @returns {Promise<*>} */ getGooglePlaceDetails (placeId, item) { return new Promise(resolve => { this.google.places.getDetails({ placeId, fields: [ 'geometry', 'address_component', ], }, place => { resolve({ ...item, address: item.address, lat: place.geometry.location.lat(), lng: place.geometry.location.lng(), parts: new PartsLegacy( place.address_components ), }); }); }); } /** * Gets the details about the given location * * @param {string} locationId * @param {Object} item * @return {Promise<*>} */ async getHerePlaceDetails (locationId, item) { const params = new URLSearchParams({ app_id: this.token.appId, app_code: this.token.appCode, locationid: locationId, jsonattributes: 1, gen: 9, language: this.locale, }).toString(); const data = await fetch( 'https://geocoder.api.here.com/6.2/geocode.json?' + params ).then(res => res.json()); const place = data.response.view[0].result[0].location; return { ...item, lat: place.displayPosition.latitude, lng: place.displayPosition.longitude, parts: new Parts( place.address, GeoService.Here ), }; } } ================================================ FILE: resources/src/components/Address.vue ================================================ ================================================ FILE: resources/src/components/Fragment.vue ================================================ ================================================ FILE: resources/src/components/Input.vue ================================================ ================================================ FILE: resources/src/components/Label.vue ================================================ ================================================ FILE: resources/src/components/Map.vue ================================================ ================================================ FILE: resources/src/components/Search.vue ================================================ ================================================ FILE: resources/src/enums/GeoService.js ================================================ const GeoService = { Nominatim: 'nominatim', Mapbox: 'mapbox', GoogleMaps: 'google', AppleMapKit: 'apple', Here: 'here', }; export default GeoService; ================================================ FILE: resources/src/enums/MapTiles.js ================================================ const MapTiles = { // Open Source // ------------------------------------------------------------------------- // Wikimedia // https://foundation.wikimedia.org/wiki/Maps_Terms_of_Use Wikimedia: 'wikimedia', // OpenStreetMaps // https://operations.osmfoundation.org/policies/tiles/ OpenStreetMap: 'openstreetmap', // Carto // https://github.com/CartoDB/basemap-styles CartoVoyager: 'carto.rastertiles/voyager', CartoPositron: 'carto.light_all', CartoDarkMatter: 'carto.dark_all', // Requires API Key (Token) // ------------------------------------------------------------------------- // Mapbox MapboxOutdoors: 'mapbox.outdoors', MapboxStreets: 'mapbox.streets', MapboxLight: 'mapbox.light', MapboxDark: 'mapbox.dark', // Google Maps GoogleRoadmap: 'google.roadmap', GoogleTerrain: 'google.terrain', GoogleHybrid: 'google.hybrid', // Apple MapKit MapKitStandard: 'mapkit.standard', MapKitMutedStandard: 'mapkit.muted', MapKitSatellite: 'mapkit.satellite', MapKitHybrid: 'mapkit.hybrid', // Here HereNormalDay: 'here.normal.day', HereNormalDayGrey: 'here.normal.day.grey', HereNormalDayTransit: 'here.normal.day.transit', HereReduced: 'here.reduced.day', HerePedestrian: 'here.pedestrian.day', HereTerrain: 'here.terrain.day', HereSatellite: 'here.satellite.day', HereHybrid: 'here.hybrid.day', }; export default MapTiles; ================================================ FILE: resources/src/filters/craft.js ================================================ export function t (message, params = null) { // @ts-ignore return window.Craft.t('simplemap', message, params); } ================================================ FILE: resources/src/helpers/createW3WGrid.js ================================================ export default function createW3WGrid (L, map) { let gridLayer; return function drawGrid () { const zoom = map.getZoom(); const loadFeatures = zoom > 17; if (loadFeatures) { // Zoom level is high enough const ne = map.getBounds().getNorthEast(); const sw = map.getBounds().getSouthWest(); // Call the what3words Grid API to obtain the grid squares within the current visble bounding box window.what3words.api .gridSectionGeoJson({ southwest: { lat: sw.lat, lng: sw.lng }, northeast: { lat: ne.lat, lng: ne.lng } }).then(data => { // If the grid layer is already present, remove it as it will need to be replaced by the new grid section if (gridLayer) map.removeLayer(gridLayer); // Create a new GeoJSON layer, based on the GeoJSON returned from the what3words API gridLayer = L.geoJSON(data, { style: () => ({ color: '#777', stroke: true, weight: 0.5 }), }).addTo(map); // eslint-disable-next-line }).catch(console.error); } else if (gridLayer) { // If the grid layer already exists, remove it as the zoom level no longer requires the grid to be displayed map.removeLayer(gridLayer); } }; } ================================================ FILE: resources/src/helpers/debounce.js ================================================ /** * ## Debounce * * A function, that, as long as it continues to be invoked, will not * be triggered. The function will be called after it stops being called for * N milliseconds. * * If `immediate` is passed, trigger the function on the leading edge, * instead of the trailing. * * ```jsx * * // ... * * * * // ... * * handleInput = debounce(e => { /* ... *\/ }); * * ``` * * @param {function} func - The function to debounce * @param {number=} wait - How long, in milliseconds, to delay between attempts * @param {boolean=} immediate - Fire on the leading edge * @returns {Function} */ export default function debounce (func, wait = 500, immediate = false) { let timeout; if (wait === 0) { return function () { func.apply(this, arguments); }; } return function () { const context = this , args = arguments; if (args[0].constructor.name === 'SyntheticEvent') args[0].persist(); const later = function () { timeout = null; if (!immediate) func.apply(context, args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } ================================================ FILE: resources/src/helpers/waitForGlobal.js ================================================ export default function waitForGlobal (property, callback) { if (window.hasOwnProperty(property)) { callback(); return; } const i = setInterval(() => { if (window.hasOwnProperty(property)) { callback(); clearTimeout(i); } }); } ================================================ FILE: resources/src/main.js ================================================ import App from './App.vue'; import { t } from './filters/craft'; const VueSimpleMapPlugin = { install (Vue) { Vue.filter('t', t); Vue.component('simple-map', App); } }; if (typeof window !== 'undefined' && window.Vue) window.Vue.use(VueSimpleMapPlugin); ================================================ FILE: resources/src/models/Parts.js ================================================ import GeoService from '../enums/GeoService'; export default class Parts { // Properties // ========================================================================= number = ''; address = ''; city = ''; postcode = ''; county = ''; state = ''; country = ''; constructor (parts, service) { switch (service) { case GeoService.Nominatim: this._nominatim(parts); break; case GeoService.Mapbox: this._mapbox(parts); break; case GeoService.GoogleMaps: this._google(parts); break; case GeoService.Here: this._here(parts); break; default: return this; } } /** * Create from existing parts (i.e. from server) * * @param parts * @return {Parts} */ static from (parts) { const p = new Parts(); p.number = parts.number || ''; p.address = parts.address || ''; p.city = parts.city || ''; p.postcode = parts.postcode || ''; p.county = parts.county || ''; p.state = parts.state || ''; p.country = parts.country || ''; return p; } // Helpers // ========================================================================= /** * Parse Nominatim parts * * @param parts * @private */ _nominatim (parts) { this.number = this._join([ parts.house_number, parts.address29, [ 'pedestrian', 'footway', 'path', 'road', 'neighbourhood', 'suburb', 'village', 'town', 'city_district', 'city', ].indexOf(parts.type) === -1 ? parts[parts.type] : null, ]); this.address = this._join([ parts.pedestrian, parts.footway, parts.path, parts.road, parts.neighbourhood, parts.suburb, ]); this.city = this._join([ parts.village, parts.town, parts.city_district, parts.city, ]); this.postcode = parts.postcode; this.county = parts.county; this.state = this._join([ parts.state_district, parts.state, ]); this.country = parts.country; } /** * Parse Mapbox parts * * @param parts * @private */ _mapbox (parts) { if (!parts.context) { this.number = null; this.address = null; this.city = null; this.postcode = null; this.county = null; this.state = null; this.country = null; return; } parts = parts.context.reduce((a, part) => { const key = part.id.split('.')[0]; a[key] = part.text; return a; }, { number: parts.address, [parts.place_type[0]]: parts.text, }); this.number = parts.number; this.address = parts.address; this.city = parts.place; this.postcode = parts.postcode; this.county = parts.district; this.state = parts.region; this.country = parts.country; } /** * Parse Google Maps parts * * @param parts * @private */ _google (parts) { if (Array.isArray(parts)) { parts = parts.reduce((a, part) => { const key = part.types[0]; a[key] = part.long_name; return a; }, {}); } this.number = this._join([ parts.subpremise, parts.premise, parts.street_number, ]); this.address = this._join([ parts.route, parts.neighborhood, parts.sublocality_level_5, parts.sublocality_level_4, parts.sublocality_level_3, parts.sublocality_level_2, parts.sublocality_level_1, parts.sublocality, ]); this.city = this._join([ parts.postal_town, parts.locality, ]); this.postcode = parts.postal_code || parts.postal_code_prefix; this.county = parts.administrative_area_level_2; this.state = parts.administrative_area_level_1; this.country = parts.country; } /** * Parse Here parts * * @param parts * @private */ _here (parts) { parts = { ...parts, ...parts.additionalData.reduce((a, b) => { a[b.key] = b.value; return a; }, {}), }; delete parts.additionalData; this.number = parts.houseNumber || ''; this.address = this._join([ parts.street, parts.district, ]); this.city = parts.city || ''; this.postcode = parts.postalCode || ''; this.county = parts.CountyName || parts.county || ''; this.state = parts.StateName || parts.state || ''; this.country = parts.CountryName || parts.country || ''; } // Helpers // ========================================================================= /** * Filters and joins the given array * * @param {array} parts * @return {string} * @private */ _join (parts) { return parts.filter(Boolean).join(', '); } } ================================================ FILE: resources/src/models/PartsLegacy.js ================================================ /* eslint-disable camelcase */ import Parts from './Parts'; import GeoService from '../enums/GeoService'; export default class PartsLegacy extends Parts { // Properties // ========================================================================= administrative_area_level_1 = ''; administrative_area_level_2 = ''; administrative_area_level_3 = ''; administrative_area_level_4 = ''; administrative_area_level_5 = ''; airport = ''; bus_station = ''; colloquial_area = ''; establishment = ''; floor = ''; intersection = ''; locality = ''; natural_feature = ''; neighborhood = ''; park = ''; parking = ''; point_of_interest = ''; political = ''; post_box = ''; postal_code = ''; postal_code_prefix = ''; postal_town = ''; premise = ''; room = ''; route = ''; street_address = ''; street_number = ''; sublocality = ''; sublocality_level_1 = ''; sublocality_level_2 = ''; sublocality_level_3 = ''; sublocality_level_4 = ''; sublocality_level_5 = ''; subpremise = ''; train_station = ''; transit_station = ''; administrative_area_level_1_short = ''; administrative_area_level_2_short = ''; administrative_area_level_3_short = ''; administrative_area_level_4_short = ''; administrative_area_level_5_short = ''; airport_short = ''; bus_station_short = ''; colloquial_area_short = ''; establishment_short = ''; floor_short = ''; intersection_short = ''; locality_short = ''; natural_feature_short = ''; neighborhood_short = ''; park_short = ''; parking_short = ''; point_of_interest_short = ''; political_short = ''; post_box_short = ''; postal_code_short = ''; postal_code_prefix_short = ''; postal_town_short = ''; premise_short = ''; room_short = ''; route_short = ''; street_address_short = ''; street_number_short = ''; sublocality_short = ''; sublocality_level_1_short = ''; sublocality_level_2_short = ''; sublocality_level_3_short = ''; sublocality_level_4_short = ''; sublocality_level_5_short = ''; subpremise_short = ''; train_station_short = ''; transit_station_short = ''; constructor (parts = {}) { super(parts, GeoService.GoogleMaps); if (Array.isArray(parts)) { parts = parts.reduce((a, part) => { const key = part.types[0]; a[key] = part.long_name; return a; }, {}); } Object.keys(parts).forEach(key => { this[key] = parts[key]; }); } } ================================================ FILE: resources/vue.config.js ================================================ module.exports = { filenameHashing: false, outputDir: '../src/web/assets/map', devServer: { https: true, headers: { "Access-Control-Allow-Origin": "*" }, disableHostCheck: true, port: 8080, }, configureWebpack: config => { config.output.library = 'EtherMaps'; // config.plugins.push( // new require('webpack-bundle-analyzer').BundleAnalyzerPlugin() // ); }, }; ================================================ FILE: src/SimpleMap.php ================================================ setComponents([ 'map' => MapService::class, 'static' => StaticService::class, 'embed' => EmbedService::class, 'geolocation' => GeoLocationService::class, ]); Event::on( UrlManager::class, UrlManager::EVENT_REGISTER_CP_URL_RULES, [$this, 'onRegisterCPUrlRules'] ); Event::on( Fields::class, Fields::EVENT_REGISTER_FIELD_TYPES, [$this, 'onRegisterFieldTypes'] ); Event::on( CraftVariable::class, CraftVariable::EVENT_INIT, [$this, 'onRegisterVariable'] ); if (class_exists(Gql::class)) { Event::on( Gql::class, Gql::EVENT_REGISTER_GQL_TYPES, [$this, 'onRegisterGqlTypes'] ); } if (class_exists(\craft\feedme\Plugin::class)) { Event::on( \craft\feedme\services\Fields::class, \craft\feedme\services\Fields::EVENT_REGISTER_FEED_ME_FIELDS, [$this, 'onRegisterFeedMeFields'] ); } $request = Craft::$app->getRequest(); if ( !$request->getIsConsoleRequest() && $request->getMethod() === 'GET' && $request->getIsSiteRequest() && !$request->getIsPreview() && !$request->getIsActionRequest() ) { Event::on( Application::class, Application::EVENT_INIT, [$this, 'onApplicationInit'] ); } if (class_exists(WpImportCommand::class)) { Event::on( WpImportCommand::class, WpImportCommand::EVENT_REGISTER_ACF_ADAPTERS, static function (RegisterComponentTypesEvent $event) { $event->types[] = GoogleMapAcfAdapter::class; } ); } } protected function beforeUninstall (): void { if ($this->getSettings()->geoLocationService === GeoLocationService::MaxMindLite) GeoLocationService::purgeDb(); } // Settings // ========================================================================= protected function createSettingsModel (): Model { return new Settings(); } /** * @return Model */ public function getSettings (): Model { return parent::getSettings(); } protected function settingsHtml (): ?string { // Redirect to our settings page Craft::$app->controller->redirect( UrlHelper::cpUrl('maps/settings') ); return null; } public function afterSaveSettings (): void { parent::afterSaveSettings(); $service = $this->getSettings()->geoLocationService; if ($service !== GeoLocationService::MaxMindLite) GeoLocationService::purgeDb(); else if (!GeoLocationService::dbExists()) GeoLocationService::dbQueueDownload(); } // Events // ========================================================================= public function onRegisterCPUrlRules (RegisterUrlRulesEvent $event) { $event->rules['maps/settings'] = 'simplemap/settings'; } public function onRegisterFieldTypes (RegisterComponentTypesEvent $event) { $event->types[] = MapField::class; } /** * @param Event $event * * @throws InvalidConfigException */ public function onRegisterVariable (Event $event) { /** @var CraftVariable $variable */ $variable = $event->sender; $variable->set('simpleMap', Variable::class); $variable->set('maps', Variable::class); } public function onRegisterFeedMeFields (\craft\feedme\events\RegisterFeedMeFieldsEvent $event) { $event->fields[] = FeedMeMaps::class; } public function onRegisterGqlTypes (RegisterGqlTypesEvent $event) { $event->types[] = MapType::class; $event->types[] = MapPartsType::class; } /** * @throws Exception */ public function onApplicationInit () { if ($this->getSettings()->geoLocationAutoRedirect) $this->geolocation->redirect(); } // Helpers // ========================================================================= public static function t ($message, $params = []): string { return Craft::t('simplemap', $message, $params); } public static function v ($version, $operator = '='): bool { return SimpleMap::getInstance()->is($version, $operator); } } ================================================ FILE: src/acfadapters/GoogleMap.php ================================================ lat = $data['center_lat']; } if ($data['center_lng']) { $field->lng = $data['center_lng']; } if ($data['zoom']) { $field->zoom = $data['zoom']; } return $field; } public function normalizeValue(mixed $value, array $data): mixed { return [ 'address' => $value['address'], 'lat' => $value['lat'], 'lng' => $value['lng'], 'zoom' => $value['zoom'], 'parts' => [ 'number' => $value['street_number'], 'address' => $value['street_name'], 'city' => $value['city'], 'postcode' => $value['post_code'], 'state' => $value['state'], 'country' => $value['country'], 'administrative_area_level_1' => $value['state'], 'locality' => $value['city'], 'postal_code' => $value['post_code'], 'route' => $value['street_name'], 'street_number' => $value['street_number'], ], ]; } } ================================================ FILE: src/config.php ================================================ MapTiles::Wikimedia, 'mapToken' => '', 'geoService' => GeoService::Nominatim, 'geoToken' => '', 'w3wToken' => '', ]; ================================================ FILE: src/controllers/SettingsController.php ================================================ renderTemplate( 'simplemap/settings', [ 'isLite' => SimpleMap::v(SimpleMap::EDITION_LITE), 'settings' => SimpleMap::getInstance()->getSettings(), 'mapTileOptions' => MapTiles::getSelectOptions(), 'geoServiceOptions' => GeoService::getSelectOptions(), 'geoLocationOptions' => GeoLocationService::getSelectOptions(), ] ); } } ================================================ FILE: src/controllers/StaticController.php ================================================ getRequest(); if (!$request->validateCsrfToken($request->getRequiredQueryParam('csrf'))) throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.')); return (new StaticMap( $request->getRequiredQueryParam('lat'), $request->getRequiredQueryParam('lng'), $request->getRequiredQueryParam('width'), $request->getRequiredQueryParam('height'), $request->getRequiredQueryParam('zoom'), $request->getRequiredQueryParam('scale'), $request->getQueryParam('markers') ))->render(); } } ================================================ FILE: src/enums/GeoService.php ================================================ SimpleMap::t('Open Source') ], self::Nominatim => SimpleMap::t('Nominatim'), [ 'optgroup' => SimpleMap::t('Requires API Key (Token)') ], self::GoogleMaps => SimpleMap::t('Google Maps'), self::Mapbox => MapTiles::pro('Mapbox', $isLite), // MapKit lacks both separate address parts and country restriction // on the front-end, and any sort of server-side API, so it's // disabled for now. // self::AppleMapKit => MapTiles::pro('Apple MapKit', $isLite), self::Here => MapTiles::pro('Here', $isLite), ]; } } ================================================ FILE: src/enums/MapTiles.php ================================================ SimpleMap::t('Open Source')], // self::Wikimedia => SimpleMap::t('Wikimedia'), self::OpenStreetMap => SimpleMap::t('OpenStreetMap'), self::CartoVoyager => SimpleMap::t('Carto: Voyager'), self::CartoPositron => SimpleMap::t('Carto: Positron'), self::CartoDarkMatter => SimpleMap::t('Carto: Dark Matter'), ['optgroup' => SimpleMap::t('Requires API Key (Token)')], self::GoogleRoadmap => SimpleMap::t('Google Maps: Roadmap'), self::GoogleTerrain => SimpleMap::t('Google Maps: Terrain'), self::GoogleHybrid => SimpleMap::t('Google Maps: Hybrid'), self::MapboxOutdoors => self::pro('Mapbox: Outdoors', $isLite), self::MapboxStreets => self::pro('Mapbox: Streets', $isLite), self::MapboxLight => self::pro('Mapbox: Light', $isLite), self::MapboxDark => self::pro('Mapbox: Dark', $isLite), self::MapKitStandard => self::pro('Apple MapKit: Standard', $isLite), self::MapKitMutedStandard => self::pro('Apple MapKit: Muted Standard', $isLite), self::MapKitSatellite => self::pro('Apple MapKit: Satellite', $isLite), self::MapKitHybrid => self::pro('Apple MapKit: Hybrid', $isLite), self::HereNormalDay => self::pro('Here: Normal Day', $isLite), self::HereNormalDayGrey => self::pro('Here: Normal Day Grey', $isLite), self::HereNormalDayTransit => self::pro('Here: Normal Day Transit', $isLite), self::HereReduced => self::pro('Here: Reduced', $isLite), self::HerePedestrian => self::pro('Here: Pedestrian', $isLite), self::HereTerrain => self::pro('Here: Terrain', $isLite), self::HereSatellite => self::pro('Here: Satellite', $isLite), self::HereHybrid => self::pro('Here: Hybrid', $isLite), ]; } /** * Get the tiles url for the given type and scale * * @param string $type * @param int $scale * * @return array * @throws Exception */ public static function getTiles (string $type, int $scale = 1): array { $scale = $scale == 1 ? '.png' : '@2x.png'; $style = str_contains($type, '.') ? explode('.', $type, 2)[1] : ''; switch ($type) { case self::Wikimedia: return [ 'url' => 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}' . $scale, 'size' => 512, ]; case self::OpenStreetMap: return [ 'url' => 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', 'size' => 256, ]; case self::CartoVoyager: case self::CartoPositron: case self::CartoDarkMatter: return [ 'url' => 'https://a.basemaps.cartocdn.com/' . $style . '/{z}/{x}/{y}' . $scale, 'size' => 256, ]; } throw new Exception('Unknown tile type "' . $type . '"'); } // Helpers // ========================================================================= public static function pro ($label, $isLite): array { return [ 'label' => SimpleMap::t($label) . ($isLite ? ' (Pro)' : ''), 'disabled' => $isLite, ]; } } ================================================ FILE: src/fields/MapField.php ================================================ $instances[0], 'value' => $value, ]; return null; } // Methods: Instance // ------------------------------------------------------------------------- public function setCountryRestriction ($value) { $this->country = $value; } public function rules (): array { $rules = parent::rules(); $rules[] = [ ['zoom', 'minZoom', 'maxZoom'], 'required', ]; $rules[] = [ ['minZoom', 'maxZoom'], 'double', 'min' => 0, 'max' => 18, ]; $rules[] = [ ['lat'], 'double', 'min' => -90, 'max' => 90, ]; $rules[] = [ ['lng'], 'double', 'min' => -180, 'max' => 180, ]; return $rules; } public function normalizeValue (mixed $value, ElementInterface|Element $element = null): Map { if (is_string($value)) $value = Json::decodeIfJson($value); if ($value instanceof Map) $map = $value; elseif (is_array($value)) $map = new Map($value); else $map = new Map([ 'lat' => null, 'lng' => null, 'zoom' => $this->zoom, ]); SimpleMap::getInstance()->map->populateMissingData($map, $this); $map->fieldId = $this->id; if ($element) { $map->ownerId = $element->id; $map->ownerSiteId = $element->siteId; $handle = $this->handle; $element->setFieldValue($handle, $map); } return $map; } public function getSettingsHtml (): string { $value = new Map(); $value->lat = $this->lat; $value->lng = $this->lng; $value->zoom = $this->zoom; $originalHandle = $this->handle; $originalCountry = $this->country; $originalHideSearch = $this->hideSearch; $originalHideMap = $this->hideMap; $originalHideAddress = $this->hideAddress; $originalShowCurrentLocation = $this->showCurrentLocation; $originalSize = $this->size; $originalShowW3WGrid = $this->showW3WGrid; $originalShowW3WField = $this->showW3WField; $this->handle = '__settings__'; $this->country = null; $this->hideSearch = false; $this->hideMap = false; $this->hideAddress = true; $this->showCurrentLocation = true; $this->size = 'normal'; $this->showW3WGrid = false; $this->showW3WField = false; $mapField = new Markup( $this->_renderMap($value, true), 'utf-8' ); $this->handle = $originalHandle; $this->country = $originalCountry; $this->hideSearch = $originalHideSearch; $this->hideMap = $originalHideMap; $this->hideAddress = $originalHideAddress; $this->showCurrentLocation = $originalShowCurrentLocation; $this->size = $originalSize; $this->showW3WGrid = $originalShowW3WGrid; $this->showW3WField = $originalShowW3WField; $view = Craft::$app->getView(); $countries = array_merge([ '*' => SimpleMap::t('All Countries'), ], GeoService::$countries); return $view->renderTemplate('simplemap/field-settings', [ 'map' => $mapField, 'field' => $this, 'countries' => $countries, 'settings' => SimpleMap::getInstance()->getSettings(), ]); } public function getInputHtml ($value = null, ElementInterface $element = null): string { if ($element !== null && $element->hasEagerLoadedElements($this->handle)) $value = $element->getEagerLoadedElements($this->handle); return new Markup( $this->_renderMap($value ?: new Map()), 'utf-8' ); } public function getTableAttributeHtml (mixed $value, ElementInterface $element): string { return $this->normalizeValue($value, $element)->address; } public function isValueEmpty ($value, ElementInterface $element): bool { return $this->normalizeValue($value)->isValueEmpty(); } // GraphQl // ------------------------------------------------------------------------- /** * @inheritdoc */ public function getContentGqlType (): Type|array { return MapType::getType(); } public function getContentGqlQueryArgumentType (): Type|array { return MapType::getQueryType(); } public function getContentGqlMutationArgumentType (): Type|array { return MapType::getInputType(); } // Methods: Events // ------------------------------------------------------------------------- public function beforeSave (bool $isNew): bool { $this->lat = (float) $this->lat; $this->lng = (float) $this->lng; $this->zoom = (int) $this->zoom; if ($this->country === '*') $this->country = null; return parent::beforeSave($isNew); } public function beforeElementSave (ElementInterface $element, bool $isNew): bool { if (!SimpleMap::getInstance()->map->validateField($this, $element)) return false; return parent::beforeElementSave($element, $isNew); } public function afterElementSave (ElementInterface $element, bool $isNew): void { SimpleMap::getInstance()->map->saveField($this, $element); parent::afterElementSave($element, $isNew); } public function afterPrepareElementQuery (CancelableEvent $event): void { if (!self::$searchParams) return; /** @var ElementQueryInterface $query */ $query = $event->sender; SimpleMap::getInstance()->map->modifyElementsQuery( $query, self::$searchParams['value'], self::$searchParams['field'], ); // Clear search params to prevent it being applied to unrelated queries. self::$searchParams = null; } // Helpers // ========================================================================= /** * Renders the map input * * @param Map $value * @param bool $isSettings * * @return string * @throws InvalidConfigException */ private function _renderMap (Map $value, bool $isSettings = false): string { $view = Craft::$app->getView(); $containerId = 'map-' . $this->id . 'container'; $vueContainerId = $view->namespaceInputId($containerId); $view->registerAssetBundle(MapAsset::class); $view->registerJs('new Vue({ el: \'#' . $vueContainerId . '\' });'); $view->registerTranslations('simplemap', [ 'Search for a location', 'Clear address', 'Full Address', 'Name / Number', 'Street Address', 'Town / City', 'Postcode', 'County', 'State', 'Country', 'Latitude', 'Longitude', 'Zoom In', 'Zoom Out', 'Center on Marker', 'Current Location', 'No address selected', 'what3words', ]); /** @var Settings $settings */ $settings = SimpleMap::getInstance()->getSettings(); $country = $this->country; // Convert ISO2 to ISO3 for Here autocomplete if ($country && $settings->geoService === GeoEnum::Here) $country = GeoService::$countriesIso3[$country]; $tiles = $settings->mapTiles; if ($tiles === MapTiles::Wikimedia) $tiles = MapTiles::OpenStreetMap; $opts = [ 'config' => [ 'isSettings' => $isSettings, 'name' => $view->namespaceInputName($this->handle), 'country' => $country, 'hideSearch' => (bool) $this->hideSearch, 'hideMap' => (bool) $this->hideMap, 'hideAddress' => (bool) $this->hideAddress, 'showLatLng' => (bool) $this->showLatLng, 'showCurrentLocation' => (bool) $this->showCurrentLocation, 'minZoom' => $isSettings ? 0 : (float) $this->minZoom, 'maxZoom' => $isSettings ? 18 : (float) $this->maxZoom, 'size' => $this->size, 'mapTiles' => $tiles, 'mapToken' => GeoService::getToken( $settings->getMapToken(), $tiles ), 'geoService' => $settings->geoService, 'geoToken' => GeoService::getToken( $settings->getGeoToken(), $settings->geoService ), 'w3wEnabled' => $settings->isW3WEnabled(), 'showW3WGrid' => (bool) $this->showW3WGrid, 'showW3WField' => (bool) $this->showW3WField, 'locale' => Craft::$app->locale->getLanguageID(), ], 'value' => [ 'address' => $value->address, 'lat' => self::_parseFloat($value->lat), 'lng' => self::_parseFloat($value->lng), 'zoom' => $value->zoom, 'parts' => $value->parts, 'what3words' => $value->what3words, ], 'defaultValue' => [ 'address' => null, 'lat' => self::_parseFloat($this->lat), 'lng' => self::_parseFloat($this->lng), 'zoom' => $this->zoom, 'parts' => null, 'what3words' => null, ], ]; // Map Services // --------------------------------------------------------------------- if (str_contains($tiles, 'google')) { if ($settings->getMapToken() !== $settings->getGeoToken()) { $view->registerJsFile( 'https://maps.googleapis.com/maps/api/js?key=' . $settings->getMapToken() ); } } elseif (str_contains($tiles, 'mapkit')) { $view->registerJsFile( 'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js' ); } // Geo Services // --------------------------------------------------------------------- if ($settings->geoService === GeoEnum::GoogleMaps) { $view->registerJsFile( 'https://maps.googleapis.com/maps/api/js?libraries=places&key=' . $settings->getGeoToken() ); } elseif ($settings->geoService === GeoEnum::AppleMapKit) { $view->registerJsFile( 'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js' ); } // what3words // --------------------------------------------------------------------- if ($settings->w3wEnabled && !empty($settings->getW3WToken())) { $view->registerJsFile( 'https://assets.what3words.com/sdk/v3/what3words.js?key=' . $settings->getW3WToken() ); } // --------------------------------------------------------------------- $options = preg_replace( '/\'/', ''', json_encode($opts) ); if ($this->size === 'normal') $children = '
'; else $children = $value->address; return '
' . $children . '
'; } // Helpers // ========================================================================= /** * Will cast the given value to a float if not null * * @param null $value * * @return float|null */ private static function _parseFloat ($value = null): ?float { if ($value === null) return null; return (float) $value; } } ================================================ FILE: src/integrations/feedme/FeedMeMaps.php ================================================ fieldInfo, 'fields'); if (!$fields) return null; foreach ($fields as $subFieldHandle => $subFieldInfo) { if ($subFieldHandle === 'parts') { foreach ($subFieldInfo as $handle => $info) $preppedData[$subFieldHandle][$handle] = DataHelper::fetchValue( $this->feedData, $info ); continue; } $preppedData[$subFieldHandle] = DataHelper::fetchValue( $this->feedData, $subFieldInfo ); } if (isset($preppedData['parts'])) $preppedData['parts'] = new Parts($preppedData['parts']); if (!$preppedData) return null; return new Map($preppedData); } } ================================================ FILE: src/integrations/graphql/MapPartsType.php ================================================ [ 'name' => 'number', 'type' => Type::string(), 'description' => 'The address name / number.', ], 'address' => [ 'name' => 'address', 'type' => Type::string(), 'description' => 'The street address.', ], 'city' => [ 'name' => 'city', 'type' => Type::string(), 'description' => 'The city.', ], 'postcode' => [ 'name' => 'postcode', 'type' => Type::string(), 'description' => 'The postal code.', ], 'county' => [ 'name' => 'county', 'type' => Type::string(), 'description' => 'The county.', ], 'state' => [ 'name' => 'state', 'type' => Type::string(), 'description' => 'The state.', ], 'country' => [ 'name' => 'country', 'type' => Type::string(), 'description' => 'The country.', ], ]; } public static function getType (): Type { if ($type = GqlEntityRegistry::getEntity(static::class)) return $type; return GqlEntityRegistry::createEntity( static::class, new ObjectType( [ 'name' => static::getName(), 'fields' => static::class . '::getFieldDefinitions', ] ) ); } public static function getInputType (): InputType { $name = static::class . 'Input'; if ($type = GqlEntityRegistry::getEntity($name)) return $type; return GqlEntityRegistry::createEntity( $name, new InputObjectType([ 'name' => static::getName() . 'Input', 'fields' => static::class . '::getFieldDefinitions', ]) ); } } ================================================ FILE: src/integrations/graphql/MapType.php ================================================ [ 'name' => 'lat', 'type' => Type::float(), 'description' => 'The maps latitude.', ], 'lng' => [ 'name' => 'lng', 'type' => Type::float(), 'description' => 'The maps longitude.', ], 'zoom' => [ 'name' => 'zoom', 'type' => Type::int(), 'description' => 'The maps zoom level.', ], 'distance' => [ 'name' => 'distance', 'type' => Type::float(), 'description' => 'The distance to this location.', ], 'address' => [ 'name' => 'address', 'type' => Type::string(), 'description' => 'The full address.', ], 'parts' => [ 'name' => 'parts', 'type' => MapPartsType::getType(), 'description' => 'The maps address parts.', ], ]; } public static function getInputDefinitions (): array { $fields = static::getFieldDefinitions(); unset($fields['distance']); $fields['parts']['type'] = MapPartsType::getInputType(); return $fields; } public static function getQueryInputDefinitions (): array { return [ 'coordinate' => [ 'name' => 'coordinate', 'type' => static::getCoordsType(), ], 'location' => [ 'name' => 'location', 'type' => Type::string(), ], 'country' => [ 'name' => 'country', 'type' => Type::string(), ], 'radius' => [ 'name' => 'radius', 'type' => Type::float(), ], 'unit' => [ 'name' => 'unit', 'type' => static::getUnitType(), ], ]; } public static function getType (): Type { if ($type = GqlEntityRegistry::getEntity(static::getName())) return $type; return GqlEntityRegistry::createEntity( static::getName(), new ObjectType([ 'name' => static::getName(), 'fields' => static::class . '::getFieldDefinitions', ]) ); } public static function getInputType (): InputType { $name = static::getName() . 'Input'; if ($type = GqlEntityRegistry::getEntity($name)) return $type; return GqlEntityRegistry::createEntity( $name, new InputObjectType([ 'name' => static::getName() . 'Input', 'fields' => static::class . '::getInputDefinitions', ]) ); } public static function getQueryType (): InputType { $name = static::getName() . 'Query'; if ($type = GqlEntityRegistry::getEntity($name)) return $type; return GqlEntityRegistry::createEntity( $name, new InputObjectType([ 'name' => static::getName() . 'Query', 'fields' => static::class . '::getQueryInputDefinitions', ]) ); } public static function getCoordsType (): InputType { $name = static::getName() . 'Coords'; if ($type = GqlEntityRegistry::getEntity($name)) return $type; return GqlEntityRegistry::createEntity( $name, new InputObjectType([ 'name' => static::getName() . 'Coords', 'fields' => [ 'lat' => [ 'name' => 'lat', 'type' => Type::nonNull(Type::float()), ], 'lng' => [ 'name' => 'lng', 'type' => Type::nonNull(Type::float()), ], ], ]) ); } public static function getUnitType (): EnumType { $name = static::getName() . 'Unit'; if ($type = GqlEntityRegistry::getEntity($name)) return $type; return GqlEntityRegistry::createEntity( $name, new EnumType([ 'name' => static::getName() . 'Unit', 'values' => [ 'Miles' => 'mi', 'Kilometres' => 'km', ], ]) ); } } ================================================ FILE: src/jobs/MaxMindDBDownloadJob.php ================================================ get( 'https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz', [ 'save_to' => $temp, 'progress' => function ($total, $current) use ($queue) { if ($total > 0) $queue->setProgress(($current / $total) * 100); }, ] ); $extracted = ''; $gz = gzopen($temp, 'r'); while ($data = gzread($gz, 10000000)) $extracted .= $data; gzclose($gz); if (!file_exists($target)) FileHelper::createDirectory(pathinfo($target)['dirname']); $saved = file_put_contents($target, $extracted); @unlink($temp); if (!$saved) Craft::error('Unable to save MaxMind DB!', 'maps'); Craft::$app->getCache()->delete('maps_db_updating'); } catch (Exception $e) { Craft::$app->getCache()->delete('maps_db_updating'); throw $e; } } } ================================================ FILE: src/migrations/Install.php ================================================ createTable( Map::TableName, [ 'id' => $this->primaryKey(), 'ownerId' => $this->integer()->notNull(), 'ownerSiteId' => $this->integer(), 'fieldId' => $this->integer()->notNull(), 'lat' => $this->decimal(11, 9), 'lng' => $this->decimal(12, 9), 'dateCreated' => $this->dateTime()->notNull(), 'dateUpdated' => $this->dateTime()->notNull(), 'uid' => $this->uid()->notNull(), ] ); // Indexes $this->createIndex( null, Map::TableName, ['ownerId', 'ownerSiteId', 'fieldId'], true ); $this->createIndex( null, Map::TableName, ['lat'] ); $this->createIndex( null, Map::TableName, ['lng'] ); // Relations $this->addForeignKey( null, Map::TableName, ['ownerId'], Table::ELEMENTS, ['id'], 'CASCADE' ); $this->addForeignKey( null, Map::TableName, ['ownerSiteId'], Table::SITES, ['id'], 'CASCADE', 'CASCADE' ); $this->addForeignKey( null, Map::TableName, ['fieldId'], Table::FIELDS, ['id'], 'CASCADE' ); // Upgrade from Craft 2 if ($this->db->tableExists('{{%simplemap_maps}}')) (new m190226_143809_craft3_upgrade())->safeUp(); } public function safeUpPre34 () { // Create $this->createTable( Map::TableName, [ 'id' => $this->primaryKey(), 'ownerId' => $this->integer()->notNull(), 'ownerSiteId' => $this->integer(), 'fieldId' => $this->integer()->notNull(), 'lat' => $this->decimal(11, 9), 'lng' => $this->decimal(12, 9), 'zoom' => $this->integer(2), 'address' => $this->string(255), 'parts' => $this->text(), 'dateCreated' => $this->dateTime()->notNull(), 'dateUpdated' => $this->dateTime()->notNull(), 'uid' => $this->uid()->notNull(), ] ); // Indexes $this->createIndex( null, Map::TableName, ['ownerId', 'ownerSiteId', 'fieldId'], true ); $this->createIndex( null, Map::TableName, ['lat'] ); $this->createIndex( null, Map::TableName, ['lng'] ); } public function safeDown () { $this->dropTableIfExists(Map::TableName); } } ================================================ FILE: src/migrations/m190226_143809_craft3_upgrade.php ================================================ db->tableExists(MapRecord::OldTableName) && !$this->db->tableExists(MapRecord::TableName)) (new Install())->safeUp(); // 2. Upgrade the data if ($this->db->tableExists('{{%simplemap_maps}}')) $this->_upgrade2(); else $this->_upgrade3(); } /** * @inheritdoc */ public function safeDown() { echo "m190226_143809_craft3_upgrade cannot be reverted.\n"; return false; } /** * Upgrade from Craft 2 * * @throws \Throwable * @throws \yii\base\Exception * @throws \yii\db\Exception */ private function _upgrade2 () { $mapService = SimpleMap::getInstance()->map; $fieldsService = Craft::$app->getFields(); // Delete the old plugin row $this->delete(Table::PLUGINS, ['handle' => 'simple-map']); // Update the old data echo ' > Start map data upgrade' . PHP_EOL; $rows = (new Query()) ->select('*') ->from('{{%simplemap_maps}}') ->all(); foreach ($rows as $row) { echo ' > Upgrade map value ' . $row['address'] . PHP_EOL; $site = $this->getSiteByLocale($row['ownerLocale']); $map = new Map(); $map->ownerId = $row['ownerId']; $map->ownerSiteId = $site->id; $map->fieldId = $row['fieldId']; $map->lat = $row['lat']; $map->lng = $row['lng']; $mapService->saveRecord($map, true); } $this->dropTable('{{%simplemap_maps}}'); // Update old field types echo ' > Upgrade map field type upgrade' . PHP_EOL; $fieldContexts = []; $fieldContextsData = (new \craft\db\Query()) ->select(['context']) ->from(['{{%fields}}']) ->all(); foreach ($fieldContextsData as $fieldData) { $fieldContexts[] = $fieldData['context']; } $fieldContexts = array_unique($fieldContexts); $fields = $fieldsService->getAllFields($fieldContexts); foreach ($fields as $field) { if ($field instanceof \craft\fields\MissingField && $field->expectedType === 'SimpleMap_Map') { echo ' > Upgrade map field ' . $field->handle . PHP_EOL; $oldSettings = Json::decodeIfJson($field->settings); $newField = new MapField([ 'id' => $field->id, 'groupId' => $field->groupId, 'name' => $field->name, 'handle' => $field->handle, 'instructions' => $field->instructions, 'searchable' => $field->searchable, 'translationMethod' => $field->translationMethod, 'translationKeyFormat' => $field->translationKeyFormat, 'lat' => $oldSettings['lat'], 'lng' => $oldSettings['lng'], 'zoom' => $oldSettings['zoom'] ?? 15, 'country' => strtoupper($oldSettings['countryRestriction'] ?? '') ?: null, 'hideMap' => $oldSettings['hideMap'], ]); $fieldsService->saveField($newField); } } // Update the plugin settings $this->updatePluginSettings(); } /** * Upgrade from SimpleMap (3.3.x) * * @throws \Throwable * @throws \yii\base\Exception */ private function _upgrade3 () { $mapService = SimpleMap::getInstance()->map; // 1. Store the old data echo ' > Start map data upgrade' . PHP_EOL; $rows = (new Query()) ->select([ 'ownerId', 'ownerSiteId', 'fieldId', 'lat', 'lng', 'zoom', 'address', 'parts', ]) ->from(MapRecord::OldTableName) ->all(); // 2. Re-create the table $this->dropTable(MapRecord::OldTableName); if (!$this->db->tableExists(MapRecord::TableName)) (new Install())->safeUpPre34(); // 3. Store the old data as new $dupeKeys = []; foreach ($rows as $row) { $key = $row['ownerId'] . '_' . $row['ownerSiteId'] . '_' . $row['fieldId']; if (in_array($key, $dupeKeys)) continue; $dupeKeys[] = $key; echo ' > Upgrade map value ' . $row['address'] . PHP_EOL; $map = new Map($row); $map->ownerId = $row['ownerId']; $map->ownerSiteId = $row['ownerSiteId']; $map->fieldId = $row['fieldId']; if (!$map->zoom) $map->zoom = 15; $mapService->saveRecord($map, true); } // 4. Update field settings echo ' > Upgrade map field type upgrade' . PHP_EOL; $rows = (new Query()) ->select(['id', 'settings', 'handle']) ->from(Table::FIELDS) ->where(['type' => MapField::class]) ->all(); foreach ($rows as $row) { echo ' > Upgrade map field ' . $row['handle'] . PHP_EOL; $id = $row['id']; $oldSettings = Json::decodeIfJson($row['settings']); $newSettings = [ 'lat' => $oldSettings['lat'], 'lng' => $oldSettings['lng'], 'zoom' => $oldSettings['zoom'] ?? 15, 'country' => strtoupper($oldSettings['countryRestriction']), 'hideMap' => $oldSettings['hideMap'], ]; $this->db->createCommand() ->update( Table::FIELDS, [ 'settings' => Json::encode($newSettings) ], compact('id') ) ->execute(); } $this->updatePluginSettings(); } // Helpers // ========================================================================= /** * Returns a site handle based on a given locale. * * @param string $locale * * @return string */ private function locale2handle (string $locale): string { if ( !preg_match('/^' . HandleValidator::$handlePattern . '$/', $locale) || in_array(strtolower($locale), HandleValidator::$baseReservedWords, true) ) { $localeParts = array_filter(preg_split('/[^a-zA-Z0-9]/', $locale)); return $localeParts ? '_' . implode('_', $localeParts) : ''; } return $locale; } /** * Gets the new site based off the old locale * * @param string $locale * * @return \craft\models\Site */ private function getSiteByLocale ($locale) { $sites = \Craft::$app->sites; if ($locale === null) return static::$sitesByOldLocale[$locale] = $sites->primarySite; if (array_key_exists($locale, static::$sitesByOldLocale)) return static::$sitesByOldLocale[$locale]; $handle = $this->locale2handle($locale); $siteId = (new Query()) ->select('id') ->from(Table::SITES) ->where(['like', 'handle', '%' . $handle]) ->column(); if (!empty($siteId)) return static::$sitesByOldLocale[$locale] = $sites->getSiteById($siteId[0]); return static::$sitesByOldLocale[$locale] = $sites->primarySite; } /** * Updates the plugins settings * @throws \craft\errors\InvalidPluginException */ private function updatePluginSettings () { echo ' > Upgrade Maps settings' . PHP_EOL; /** @var Settings $settings */ $settings = SimpleMap::getInstance()->getSettings()->toArray(); $newSettings = SimpleMap::getInstance()->getSettings()->toArray(); $craft2Settings = \Craft::$app->projectConfig->get( Plugins::CONFIG_PLUGINS_KEY . '.simple-map.settings' ); if (is_array($craft2Settings) && !empty($craft2Settings)) { $settings = [ 'apiKey' => @$craft2Settings['browserApiKey'] ?: '', 'unrestrictedApiKey' => @$craft2Settings['serverApiKey'] ?: '', ]; } if ($settings['unrestrictedApiKey']) { $newSettings['geoService'] = GeoService::GoogleMaps; $newSettings['geoToken'] = $settings['unrestrictedApiKey']; } if ($settings['apiKey']) { $newSettings['mapTiles'] = MapTiles::GoogleRoadmap; $newSettings['mapToken'] = $settings['apiKey']; if (!$settings['unrestrictedApiKey']) { $newSettings['geoService'] = GeoService::GoogleMaps; $newSettings['geoToken'] = $settings['apiKey']; } } \Craft::$app->plugins->savePluginSettings( SimpleMap::getInstance(), $newSettings ); \Craft::$app->plugins->enablePlugin(SimpleMap::getInstance()->handle); } } ================================================ FILE: src/migrations/m190325_130533_repair_map_elements.php ================================================ db->columnExists(Map::TableName, 'elementId')) return true; echo ' > Start map data fix' . PHP_EOL; $rows = (new Query()) ->select('*') ->from(Map::TableName) ->orderBy('dateUpdated DESC') ->all(); $validMapElementIds = (new Query()) ->select('id') ->from(Table::ELEMENTS) ->where(['=', 'type', MapElement::class]) ->column(); $this->dropTable(Map::TableName); (new Install())->safeUp(); $updatedElementIds = []; foreach ($rows as $row) { // Skip any rows that don't have a matching element if (!in_array($row['elementId'], $validMapElementIds)) continue; // Skip and duplicate elements if (in_array($row['elementId'], $updatedElementIds)) continue; echo ' > Fix map value ' . $row['address'] . PHP_EOL; $record = new Map(); $record->id = $row['elementId']; $record->ownerId = $row['ownerId']; $record->ownerSiteId = $row['ownerSiteId']; $record->fieldId = $row['fieldId']; $record->lat = $row['lat']; $record->lng = $row['lng']; $record->zoom = $row['zoom']; $record->address = $row['address']; $record->parts = $row['parts']; $record->save(false); $updatedElementIds[] = $record->id; } return true; } /** * @inheritdoc */ public function safeDown() { echo "m190325_130533_repair_map_elements cannot be reverted.\n"; return false; } } ================================================ FILE: src/migrations/m190712_104805_new_data_format.php ================================================ getDb(); $mapService = SimpleMap::getInstance()->map; $matrixService = Craft::$app->getMatrix(); $superTableService = null; $hasSuperTable = class_exists(SuperTableField::class); // 1. Add content columns // --------------------------------------------------------------------- echo '1. Creating Maps content columns' . PHP_EOL; $matrixFields = []; $superTableFields = []; $fields = array_reduce( Craft::$app->getFields()->getAllFields(), function ($carry, FieldInterface $field) use ($hasSuperTable, &$matrixFields, &$superTableFields) { if ($field instanceof MapField) $carry[$field->id] = $field; elseif ($field instanceof Matrix) $matrixFields[] = $field; elseif ($hasSuperTable && $field instanceof SuperTableField) $superTableFields[] = $field; return $carry; }, [] ); $matrixMapFields = []; $superTableMapFields = []; $matrixMapFields = array_merge( $matrixMapFields, $this->_reduceMatrixFields( $matrixFields, $hasSuperTable, $matrixMapFields, $superTableMapFields ) ); $superTableMapFields = array_merge( $superTableMapFields, $this->_reduceSuperTableFields( $superTableFields, $matrixMapFields, $superTableMapFields ) ); $fieldIdToMatrixBlockHandle = []; if (!empty($matrixMapFields)) { foreach ($matrixFields as $field) { $blockTypes = $matrixService->getBlockTypesByFieldId($field->id); foreach ($blockTypes as $blockType) foreach ($blockType->getFields() as $field) $fieldIdToMatrixBlockHandle[$field->id] = $blockType->handle; } } $columnType = (new MapField())->getContentColumnType(); $contentTable = Craft::$app->getContent()->contentTable; $fieldColumnPrefix = Craft::$app->getContent()->fieldColumnPrefix; /** @var MapField $field */ foreach ($fields as $field) { echo '- Create content column for ' . $field->name . ' in content table' . PHP_EOL; $exists = $this->db->columnExists( $contentTable, $fieldColumnPrefix . $field->handle ); if ($exists) { $this->alterColumn( $contentTable, $fieldColumnPrefix . $field->handle, $columnType ); continue; } $this->addColumn( $contentTable, $fieldColumnPrefix . $field->handle, $columnType ); } foreach ($matrixMapFields as $table => $mmFields) { foreach ($mmFields as $field) { if (!$blockTypeHandle = @$fieldIdToMatrixBlockHandle[$field->id]) continue; echo '- Create content column for ' . $field->name . ' in matrix ' . $blockTypeHandle . PHP_EOL; $handle = $fieldColumnPrefix . $blockTypeHandle . '_' . $field->handle; $exists = $this->db->columnExists( $table, $handle ); if ($exists) { $this->alterColumn( $table, $handle, $columnType ); continue; } $this->addColumn( $table, $handle, $columnType ); } } foreach ($superTableMapFields as $table => $stFields) { foreach ($stFields as $field) { echo '- Create content column for ' . $field->name . ' in super table' . PHP_EOL; $exists = $this->db->columnExists( $table, $fieldColumnPrefix . $field->handle ); if ($exists) { $this->alterColumn( $table, $fieldColumnPrefix . $field->handle, $columnType ); continue; } $this->addColumn( $table, $fieldColumnPrefix . $field->handle, $columnType ); } } // 2. Create new maps table // --------------------------------------------------------------------- echo '2. Creating new Maps table' . PHP_EOL; if ($this->db->tableExists(MapRecord::TableName)) { $rawTableName = $this->getDb()->getSchema()->getRawTableName( MapRecord::TableName ); if ($this->getDb()->getDriverName() === 'pgsql') { $indexNames = $this->getDb()->createCommand( 'SELECT indexname FROM pg_indexes WHERE [[tablename]]=:tablename', ['tablename' => $rawTableName] )->queryColumn(); foreach ($indexNames as $name) $this->getDb()->createCommand( 'ALTER INDEX "' . $name . '" RENAME TO "' . $name . '_old"' )->execute(); } else { $indexNames = $this->getDb()->createCommand( 'SHOW INDEX FROM ' . $rawTableName )->queryAll(); $indexNames = array_unique(array_reduce( $indexNames, function ($carry, $row) { if ($row['Key_name'] === 'PRIMARY') return $carry; $carry[] = $row['Key_name']; return $carry; }, [] )); // Look, I know this is hacky but whatever who still uses MySQL // anyway? You know Postgres exists, right? $this->getDb()->createCommand('SET foreign_key_checks = 0;')->execute(); foreach ($indexNames as $name) $this->dropIndex($name, MapRecord::TableName); $this->getDb()->createCommand('SET foreign_key_checks = 1;')->execute(); } $this->renameTable(MapRecord::TableName, MapRecord::OldTableName); } (new Install())->safeUp(); // 3. Move content to table // --------------------------------------------------------------------- echo '3. Moving existing maps content' . PHP_EOL; $contentRows = (new Query()) ->select('id, elementId, siteId') ->from($contentTable); foreach ($contentRows->each() as $row) { $mapContent = $this->_getMapContent( $row['elementId'], $row['siteId'] ); if (empty($mapContent)) continue; foreach ($mapContent as $mapData) { $map = new Map($mapData); echo '- Moving ' . $map->address . ' (' . $mapData['id'] . ') to ' . $contentTable . PHP_EOL; $map->ownerId = $row['elementId']; $map->ownerSiteId = $row['siteId']; $map->fieldId = $mapData['fieldId']; $field = @$fields[$mapData['fieldId']]; // Skip if the field no longer exists if (!$field) { echo '- Skipping ' . $map->address . ' (' . $mapData['id'] . ') - Field no longer exists' . PHP_EOL; continue; } $col = $fieldColumnPrefix . $field->handle; $db->createCommand() ->update( $contentTable, [$col => Json::encode($map)], ['id' => $row['id']] ) ->execute(); $mapService->saveRecord($map, true); } } foreach ($matrixMapFields as $contentTable => $fields) { $contentRows = (new Query()) ->select('id, elementId, siteId') ->from($contentTable); foreach ($contentRows->each() as $row) { $mapContent = $this->_getMapContent( $row['elementId'], $row['siteId'] ); if (empty($mapContent)) continue; foreach ($mapContent as $mapData) { if (!$blockHandle = @$fieldIdToMatrixBlockHandle[$mapData['fieldId']]) continue; $map = new Map($mapData); $map->ownerId = $row['elementId']; $map->ownerSiteId = $row['siteId']; $map->fieldId = $mapData['fieldId']; $field = $fields[$mapData['fieldId']]; $col = $fieldColumnPrefix . $blockHandle . '_' . $field->handle; echo '- Moving ' . $map->address . ' (' . $mapData['id'] . ') to ' . $contentTable . ', ' . $col . PHP_EOL; $db->createCommand() ->update( $contentTable, [$col => Json::encode($map)], ['id' => $row['id']] ) ->execute(); $mapService->saveRecord($map, true); } } } foreach ($superTableMapFields as $contentTable => $fields) { $contentRows = (new Query()) ->select('id, elementId, siteId') ->from($contentTable); foreach ($contentRows->each() as $row) { $mapContent = $this->_getMapContent( $row['elementId'], $row['siteId'] ); if (empty($mapContent)) continue; foreach ($mapContent as $mapData) { $map = new Map($mapData); $map->ownerId = $row['elementId']; $map->ownerSiteId = $row['siteId']; $map->fieldId = $mapData['fieldId']; $field = $fields[$mapData['fieldId']]; $col = $fieldColumnPrefix . $field->handle; echo '- Moving ' . $map->address . ' (' . $mapData['id'] . ') to ' . $contentTable . ', ' . $col . PHP_EOL; $db->createCommand() ->update( $contentTable, [$col => Json::encode($map)], ['id' => $row['id']] ) ->execute(); $mapService->saveRecord($map, true); } } } // 4. Drop old data table // --------------------------------------------------------------------- $this->dropTableIfExists(MapRecord::OldTableName); } /** * @inheritdoc */ public function safeDown() { echo "m190712_104805_new_data_format cannot be reverted.\n"; return false; } // Helpers // ========================================================================= private function _reduceMatrixFields ($matrixFields, $hasSuperTable, &$matrixMapFields, &$superTableMapFields) { return array_reduce( $matrixFields, function ($carry, Matrix $matrix) use ( $hasSuperTable, &$matrixMapFields, &$superTableMapFields ) { $fields = []; foreach ($matrix->getBlockTypeFields() as $field) { if ($field instanceof MapField) $fields[$field->id] = $field; elseif ($hasSuperTable && $field instanceof SuperTableField) $superTableMapFields = array_merge( $superTableMapFields, $this->_reduceSuperTableFields( [$field], $matrixMapFields, $superTableMapFields ) ); } if (!empty($fields)) $carry[$matrix->contentTable] = $fields; return $carry; }, [] ); } private function _reduceSuperTableFields ($superTableFields, &$matrixMapFields, &$superTableMapFields) { return array_reduce( $superTableFields, function ($carry, SuperTableField $superTable) use ( &$matrixMapFields, &$superTableMapFields ) { $fields = []; foreach ($superTable->getBlockTypeFields() as $field) { if ($field instanceof MapField) $fields[$field->id] = $field; elseif ($field instanceof Matrix) $matrixMapFields = array_merge( $matrixMapFields, $this->_reduceMatrixFields( [$field], true, $matrixMapFields, $superTableMapFields ) ); } if (!empty($fields)) $carry[$superTable->contentTable] = $fields; return $carry; }, [] ); } private function _getMapContent ($elementId, $siteId) { return (new Query()) ->select( 'id, ownerId, ownerSiteId, fieldId, lat, lng, zoom, address, parts' ) ->from(MapRecord::OldTableName) ->where([ 'ownerId' => $elementId, 'ownerSiteId' => $siteId, ]) ->groupBy('id, fieldId') ->orderBy('dateUpdated') ->all(); } } ================================================ FILE: src/migrations/m190723_105637_fix_map_field_column_type.php ================================================ getProjectConfig(); $fields = $pc->get('fields', true) ?? []; $matrixBlockTypes = $pc->get('matrixBlockTypes', true) ?? []; $superTableBlockTypes = $pc->get('superTableBlockTypes', true) ?? []; $updates = []; foreach ($fields as $f => $field) if (!empty($field['type']) && $field['type'] === MapField::class && $field['contentColumnType'] !== 'text') $updates["fields.$f.contentColumnType"] = 'text'; foreach ($matrixBlockTypes as $b => $blockType) if (array_key_exists('fields', $blockType) && is_array($blockType['fields'])) foreach ($blockType['fields'] as $f => $field) if ($field['type'] === MapField::class && $field['contentColumnType'] !== 'text') $updates["matrixBlockTypes.$b.fields.$f.contentColumnType"] = 'text'; foreach ($superTableBlockTypes as $b => $blockType) if (array_key_exists('fields', $blockType) && is_array($blockType['fields'])) foreach ($blockType['fields'] as $f => $field) if ($field['type'] === MapField::class && $field['contentColumnType'] !== 'text') $updates["superTableBlockTypes.$b.fields.$f.contentColumnType"] = 'text'; foreach ($updates as $path => $value) $pc->set($path, $value); } /** * @inheritdoc */ public function safeDown () { echo "m190723_105637_fix_map_field_column_type cannot be reverted.\n"; return false; } } ================================================ FILE: src/models/BaseLocation.php ================================================ address === null) $this->address = ''; if ($this->parts === null) { $this->parts = new Parts(); } else if (!($this->parts instanceof Parts)) { if ($this->parts && !is_array($this->parts)) $this->parts = Json::decodeIfJson($this->parts, true); if (Parts::isLegacy($this->parts)) $this->parts = new PartsLegacy($this->parts); else $this->parts = new Parts($this->parts); } } // Methods // ========================================================================= /** * Output the address in an easily formatted way * * @param array $exclude - An array of parts to exclude from the output * @param string $glue - The glue to join the parts together * * @return Markup */ public function address (array $exclude = [], string $glue = '
'): Markup { $addr = []; if (!is_array($exclude)) $exclude = [$exclude]; foreach ([['number', 'address'], 'city', 'county', 'state', 'postcode', 'country'] as $part) { if (is_array($part)) { $line = []; foreach ($part as $p) { if (in_array($p, $exclude)) continue; $line[] = $this->parts->$p; } $addr[] = implode(' ', array_filter($line)); continue; } if (in_array($part, $exclude)) continue; $addr[] = $this->parts->$part; } $addr = array_filter($addr); return new Markup(implode($glue, $addr), 'utf8'); } public function __toString(): string { return (string) $this->address([], ', '); } } ================================================ FILE: src/models/EmbedOptions.php ================================================ id) $this->id = StringHelper::appendUniqueIdentifier('map'); } } ================================================ FILE: src/models/Map.php ================================================ distance = SimpleMap::getInstance()->map->getDistance($this); } // Getters // ========================================================================= public function __get ($name) { $isPart = property_exists($this->parts, $name) || $name === 'streetAddress'; if (in_array($name, PartsLegacy::$legacyKeys) && !$isPart) return null; else if ($isPart) return $this->parts->$name; return parent::__get($name); } public function canGetProperty ($name, $checkVars = true, $checkBehaviors = true): bool { try { if ( property_exists($this->parts, $name) || $name === 'streetAddress' || in_array($name, PartsLegacy::$legacyKeys) ) return true; } catch (Exception $e) { return false; } return parent::canGetProperty($name, $checkVars, $checkBehaviors); } // Methods // ========================================================================= public function rules (): array { $rules = parent::rules(); $rules[] = [ ['zoom'], 'required', ]; $rules[] = [ ['lat'], 'double', 'min' => -90, 'max' => 90, ]; $rules[] = [ ['lng'], 'double', 'min' => -180, 'max' => 180, ]; return $rules; } public function isValueEmpty (): bool { return empty($this->lat) && empty($this->lng); } // Render Map // ========================================================================= // Render Map: Image // ------------------------------------------------------------------------- /** * Output the map field as a static image * * @param array $options * * @return string|void * @throws Exception */ public function img (array $options = []) { return SimpleMap::getInstance()->static->generate( $this->_getMapOptions($options) ); } /** * Output the map ready for srcset * * @param array $options * * @return string * @throws Exception */ public function imgSrcSet (array $options = []): string { $options = $this->_getMapOptions($options); $x1 = $this->img(array_merge($options, ['scale' => 1])); $x2 = $this->img(array_merge($options, ['scale' => 2])); return $x1 . ' 1x, ' . $x2 . ' 2x'; } /** * Output an interactive map * * @param array $options * * @return string|void * @throws InvalidConfigException */ public function embed (array $options = []) { $options = $this->_getMapOptions($options); return SimpleMap::getInstance()->embed->embed($options); } // Helpers // ========================================================================= /** * Merge options w/ map properties * * @param array $options * * @return array */ private function _getMapOptions (array $options): array { return array_merge($options, [ 'center' => [ $this->lat, $this->lng, ], 'zoom' => $this->zoom, ]); } } ================================================ FILE: src/models/Marker.php ================================================ lat, 'lng' => lng] array */ public string|array $location; /** @var string The colour of the marker in Hex format */ public string $color = '#ff0000'; /** @var string|null A single character label, or null for no label */ public mixed $label = null; // Constructor // ========================================================================= public function __construct ($config = []) { if (!empty($config)) Yii::configure($this, $config); if (empty($this->location)) throw new InvalidConfigException('Marker location is missing!'); if (empty($this->color)) throw new InvalidConfigException('Marker colour is missing!'); $this->color = strtolower($this->color); if (preg_match('/^#[a-z0-9]{3}$/', $this->color)) $this->color = Marker::_expandHex($this->color); if ($this->label === '') $this->label = null; if ($this->label !== null && strlen($this->label) > 1) $this->label = $this->label[0]; } // Methods // ========================================================================= public function __toString () { return implode('|', [ Json::encode($this->location), $this->color, $this->label, ]); } public function getLocation ($toLatLng = false): array|string { if (is_string($this->location)) return $toLatLng ? implode(',', array_values(GeoService::latLngFromAddress($this->location))) : $this->location; return implode(',', array_values($this->location)); } /** * @return array|string|null * @throws Exception */ public function getCenter (): array|string|null { if (is_string($this->location)) return GeoService::latLngFromAddress($this->location); if (!array_key_exists('lat', $this->location) || !array_key_exists('lng', $this->location)) return ['lat' => (float)$this->location[0], 'lng' => (float)$this->location[1]]; return $this->location; } // Helpers // ========================================================================= private static function _expandHex ($hex): string { $r = $hex[1]; $g = $hex[2]; $b = $hex[3]; return '#' . $r . $r . $g . $g . $b . $b; } } ================================================ FILE: src/models/Parts.php ================================================ _nominatim($parts); break; case GeoService::Mapbox: $this->_mapbox($parts); break; case GeoService::GoogleMaps: $this->_google($parts); break; case GeoService::Here: $this->_here($parts); break; default: $this->_fromArray($parts); } } public function getStreetAddress (): string { return $this->address; } // Methods: Private // ------------------------------------------------------------------------- /** * Parse Nominatim parts * * @param array $parts */ private function _nominatim (array $parts) { // Add any missing values $keys = [ 'house_number', 'address29', 'type', 'pedestrian', 'footway', 'path', 'road', 'neighbourhood', 'suburb', 'village', 'town', 'city_district', 'city', 'postcode', 'county', 'state_district', 'state', 'country', ]; foreach ($keys as $key) if (!array_key_exists($key, $parts)) $parts[$key] = null; $this->number = $this->_join([ $parts['house_number'], $parts['address29'], in_array($parts['type'], [ 'pedestrian', 'footway', 'path', 'road', 'neighbourhood', 'suburb', 'village', 'town', 'city_district', 'city', ]) ? $parts[$parts['type']] : null, ]); $this->address = $this->_join([ $parts['pedestrian'], $parts['footway'], $parts['path'], $parts['road'], $parts['neighbourhood'], $parts['suburb'], ]); $this->city = $this->_join([ $parts['village'], $parts['town'], $parts['city_district'], $parts['city'], ]); $this->postcode = $parts['postcode']; $this->county = $parts['county']; $this->state = $this->_join([ $parts['state_district'], $parts['state'], ]); $this->country = $parts['country']; } /** * Parse Mapbox parts * * @param array $parts */ private function _mapbox (array $parts) { $parts = array_reduce( $parts['context'], function ($a, $part) { $key = explode('.', $part['id'])[0]; $a[$key] = $part['text']; return $a; }, [ 'number' => @$parts['address'], $parts['place_type'][0] => $parts['text'], ] ); $this->number = @$parts['number']; $this->address = @$parts['address']; $this->city = @$parts['city']; $this->postcode = @$parts['postcode']; $this->county = @$parts['county']; $this->state = @$parts['state']; $this->country = @$parts['country']; } /** * Parse Google parts * * @param $parts */ private function _google ($parts) { if (!$this->_isAssoc($parts)) { $parts = array_reduce( $parts, function ($a, $part) { $key = $part['types'][0]; $a[$key] = $part['long_name']; return $a; }, [] ); } foreach (PartsLegacy::$legacyKeys as $key) if (!array_key_exists($key, $parts)) $parts[$key] = ''; $this->number = $parts['number'] ?? $this->_join([ $parts['subpremise'], $parts['premise'], $parts['street_number'], ]); $this->address = $parts['address'] ?? $this->_join([ $parts['route'], $parts['neighborhood'], $parts['sublocality_level_5'], $parts['sublocality_level_4'], $parts['sublocality_level_3'], $parts['sublocality_level_2'], $parts['sublocality_level_1'], $parts['sublocality'], ]); $this->city = $parts['city'] ?? $this->_join([ $parts['postal_town'], $parts['locality'], ]); $this->postcode = $parts['postcode'] ?? $parts['postal_code'] ?? $parts['postal_code_prefix']; $this->county = $parts['county'] ?? $parts['administrative_area_level_2']; $this->state = $parts['state'] ?? $parts['administrative_area_level_1']; $this->country = $parts['country']; } /** * Parse Here parts * * @param $parts */ private function _here ($parts) { $parts = array_merge( $parts, array_reduce($parts['additionalData'], function ($a, $b) { $a[$b['key']] = $b['value']; return $a; }, []) ); $this->number = $parts['number']; $this->address = $this->_join([ $parts['street'], $parts['district'], ]); $this->city = $parts['city']; $this->postcode = $parts['postalCode']; $this->county = $parts['CountyName'] ?? $parts['county']; $this->state = $parts['StateName'] ?? $parts['state']; $this->country = $parts['CountryName'] ?? $parts['country']; } // Methods: Helpers // ------------------------------------------------------------------------- /** * Determines if the given array of parts contains legacy data * * @param array|null $parts * * @return bool */ public static function isLegacy (array $parts = null): bool { if ($parts === null) return false; $keys = PartsLegacy::$legacyKeys; unset($keys[array_search('country', $keys)]); foreach ($keys as $key) if (isset($parts[$key]) || array_key_exists($key, $parts)) return true; return false; } /** * Filters and joins the given array * * @param array $parts * * @return string */ private function _join (array $parts): string { return implode(', ', array_filter($parts)); } /** * Populates Parts from the given array * * @param array $parts */ private function _fromArray (array $parts) { $this->number = $parts['number'] ?? ''; $this->address = $parts['address'] ?? ''; $this->city = $parts['city'] ?? ''; $this->postcode = $parts['postcode'] ?? ''; $this->county = $parts['county'] ?? ''; $this->state = $parts['state'] ?? ''; $this->country = $parts['country'] ?? ''; } /** * Returns true if the given array is associative * * @param array $arr * * @return bool */ protected function _isAssoc (array $arr): bool { if ([] === $arr) return false; return array_keys($arr) !== range(0, count($arr) - 1); } } ================================================ FILE: src/models/PartsLegacy.php ================================================ _isAssoc($parts)) { $parts = array_reduce( $parts, function ($a, $part) { $key = $part['types'][0]; $a[$key] = $part['long_name']; return $a; }, [] ); } \Yii::configure($this, $parts); parent::__construct($parts, GeoService::GoogleMaps); } public function __set ($name, $value) { // Prevent setting any new parameters that we don't support if (!$this->hasProperty($name)) { $name = Json::encode($name); $value = Json::encode($value); Craft::info( 'Attempted to set unsupported legacy part: "' . $name . '" to value "' . $value . '"', 'simplemap' ); return; } parent::__set($name, $value); } } ================================================ FILE: src/models/Point.php ================================================ x = $x; $this->y = $y; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::getX() */ public function getX (): int { return $this->x; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::getY() */ public function getY (): int { return $this->y; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::in() */ public function in (BoxInterface $box): bool { return $this->x < $box->getWidth() && $this->y < $box->getHeight(); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::move() */ public function move ($amount): Point|PointInterface { return new self($this->x + $amount, $this->y + $amount); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::__toString() */ public function __toString () { return sprintf('(%d, %d)', $this->x, $this->y); } } ================================================ FILE: src/models/Settings.php ================================================ [ 'countryCode' => 'uk' ], * 'eu' => [ 'isEU' => true ], * 'global' => '*', * ] */ public array $geoLocationRedirectMap = []; // Methods // ========================================================================= public function __construct ($config = []) { parent::__construct($config); try { $this->geoLocationCacheDuration = ConfigHelper::durationInSeconds( $this->geoLocationCacheDuration ); } catch (Exception $e) { Craft::error($e->getMessage()); } } public function isW3WEnabled (): bool { return $this->w3wEnabled && SimpleMap::v(SimpleMap::EDITION_PRO); } // Getters // ========================================================================= public function getMapToken (): bool|array|string|null { return $this->_parseEnv($this->mapToken); } public function getGeoToken (): bool|array|string|null { return $this->_parseEnv($this->geoToken); } public function getW3WToken (): bool|array|string|null { return $this->_parseEnv($this->w3wToken); } public function getGeoLocationToken (): bool|array|string|null { return $this->_parseEnv($this->geoLocationToken); } // Helpers // ========================================================================= private function _parseEnv ($value): array|bool|string|null { if (is_string($value)) return App::parseEnv($value); return array_map(function ($v) { return App::parseEnv($v); }, $value); } } ================================================ FILE: src/models/StaticOptions.php ================================================ lat, 'lng' => lng] array */ public string|array $center = [51.272154, 0.514951]; /** @var string|array Must be [lat, lng] or ['lat' => lat, 'lng' => lng] array */ public string|array $centerFallback = [51.272154, 0.514951]; /** @var int The width of the map */ public int $width = 640; /** @var int The height of the map */ public int $height = 480; /** @var int The maps zoom level */ public int $zoom = 12; /** @var int The scale of the map image (i.e. 2 for @2x retina screens) */ public int $scale = 1; /** * @var Marker[] An array of map markers */ public array $markers = []; // Constructor // ========================================================================= /** * StaticOptions constructor. * * @param array $config * * @throws InvalidConfigException * @throws Exception */ public function __construct (array $config = []) { $center = $config['center'] ?? null; if ($center instanceof Map) $center = ['lat' => $center->lat, 'lng' => $center->lng, 'zoom' => $center->zoom]; elseif ($center instanceof UserLocation) $center = ['lat' => $center->lat, 'lng' => $center->lng]; elseif (is_string($center)) $center = GeoService::latLngFromAddress($center); if (empty($center)) $center = $config['centerFallback'] ?? $this->centerFallback; $config['center'] = $center; $markers = $config['markers'] ?? []; unset($config['markers']); if (!empty($config)) Yii::configure($this, $config); foreach (['center', 'zoom', 'scale'] as $key) if (empty($this->$key)) throw new InvalidConfigException('Map ' . $key . ' is missing!'); if (!empty($markers)) { foreach ($markers as $marker) { if (!array_key_exists('location', $marker) || empty($marker['location'])) $marker['location'] = $this->center; $this->markers[] = new Marker($marker); } } } // Getters // ========================================================================= /** * @return array|string|null * @throws Exception */ public function getCenter (): array|string|null { if (!array_key_exists('lat', $this->center) || !array_key_exists('lng', $this->center)) $this->center = ['lat' => $this->center[0], 'lng' => $this->center[1]]; $this->center['lat'] = floatval($this->center['lat']); $this->center['lng'] = floatval($this->center['lng']); return $this->center; } public function getSize (): string { return ($this->width ?? 0) . 'x' . ($this->height ?? 0); } } ================================================ FILE: src/models/UserLocation.php ================================================ lat)) * cos(deg2rad($targetLat)) * cos(deg2rad($this->lng) - deg2rad($targetLng)) + sin(deg2rad($this->lat)) * sin(deg2rad($targetLat)) ) ) ); } } ================================================ FILE: src/records/Map.php ================================================ hasOne(Element::class, ['id' => 'ownerId']); } public function getOwnerSite (): ActiveQueryInterface { return $this->hasOne(Site::class, ['id' => 'ownerSiteId']); } public function getField (): ActiveQueryInterface { return $this->hasOne(Field::class, ['id' => 'fieldId']); } } ================================================ FILE: src/resources/OpenSans_LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: src/services/EmbedService.php ================================================ getSettings(); switch ($settings->mapTiles) { case MapTiles::GoogleHybrid: case MapTiles::GoogleRoadmap: case MapTiles::GoogleTerrain: $code = $this->_embedGoogle($options, $settings); break; case MapTiles::MapKitHybrid: case MapTiles::MapKitMutedStandard: case MapTiles::MapKitSatellite: case MapTiles::MapKitStandard: $code = $this->_embedApple($options, $settings); break; case MapTiles::MapboxDark: case MapTiles::MapboxLight: case MapTiles::MapboxOutdoors: case MapTiles::MapboxStreets: $code = $this->_embedMapbox($options, $settings); break; case MapTiles::HereHybrid: case MapTiles::HereNormalDay: case MapTiles::HereNormalDayGrey: case MapTiles::HereNormalDayTransit: case MapTiles::HerePedestrian: case MapTiles::HereReduced: case MapTiles::HereSatellite: case MapTiles::HereTerrain: $code = $this->_embedHere($options, $settings); break; default: $code = $this->_embedDefault($options, $settings); } return Template::raw($code); } // Embed-ers // ========================================================================= /** * @param EmbedOptions $options * @param Settings $settings * * @return string * @throws Exception */ private function _embedGoogle (EmbedOptions $options, Settings $settings): string { $view = Craft::$app->getView(); $callbackName = 'init_' . $options->id; $loadedCallbackName = $options->id . '_loaded'; $mapTypeId = match ($settings->mapTiles) { MapTiles::GoogleRoadmap => 'roadmap', MapTiles::GoogleTerrain => 'terrain', default => 'hybrid', }; $formattedOptions = Json::encode( array_merge( [ 'zoom' => $options->zoom, ], $options->options, [ 'center' => $options->getCenter(), 'mapTypeId' => $mapTypeId, ] ), self::JSON_OPTS ); $formattedMarkers = []; foreach ($options->markers as $marker) $formattedMarkers[] = [ 'position' => $marker->getCenter(), 'label' => $marker->label, // TODO: Add custom colour support ]; $formattedMarkers = Json::encode( $formattedMarkers, self::JSON_OPTS ); $params = http_build_query([ 'key' => $settings->getMapToken(), 'callback' => $callbackName, 'language' => Craft::$app->getSites()->getCurrentSite()->language, ]); $js = <<id}; function {$callbackName} () { {$options->id} = new google.maps.Map(document.getElementById('{$options->id}'), $formattedOptions); {$options->id}._markers = []; {$formattedMarkers}.forEach(function (marker) { marker.map = {$options->id}; {$options->id}._markers.push(new google.maps.Marker(marker)); }); } JS; $view->registerScript($js, View::POS_END); $this->_js( 'https://maps.googleapis.com/maps/api/js?' . $params, ['async' => '', 'defer' => '', 'onload' => "typeof {$loadedCallbackName} != 'undefined' && {$loadedCallbackName}()"] ); $css = $this->_getCss($options); $css && $view->registerCss($css); return '
'; } /** * @param EmbedOptions $options * @param Settings $settings * * @return string * @throws Exception */ private function _embedApple (EmbedOptions $options, Settings $settings): string { $view = Craft::$app->getView(); $token = GeoService::getToken( $settings->getMapToken(), $settings->mapTiles ); $latLng = implode(', ', array_values($options->getCenter())); $formattedOptions = Json::encode( array_merge( $options->options, [ 'center' => '##CENTER##', 'cameraDistance' => '##ZOOM##', 'mapType' => '##MAPTYPE##', ] ), self::JSON_OPTS ); switch ($settings->mapTiles) { default: case MapTiles::MapKitStandard: $type = 'Standard'; break; case MapTiles::MapKitSatellite: $type = 'Satellite'; break; case MapTiles::MapKitMutedStandard: $type = 'MutedStandard'; break; case MapTiles::MapKitHybrid: $type = 'Hybrid'; break; } $formattedOptions = str_replace([ '"##CENTER##"', '"##ZOOM##"', '"##MAPTYPE##"', ], [ 'new mapkit.Coordinate(' . $latLng . ')', '156543.03392 * Math.cos(' . $options->getCenter()['lat'] . ' * Math.PI / 180) / Math.pow(2, ' . $options->zoom . ') * ' . $options->width, 'mapkit.Map.MapTypes.' . $type, ], $formattedOptions); $formattedMarkers = []; foreach ($options->markers as $marker) $formattedMarkers[] = [ 'position' => array_values($marker->getCenter()), 'label' => $marker->label, 'color' => $marker->color, ]; $formattedMarkers = Json::encode( $formattedMarkers, self::JSON_OPTS ); $initJs = <<id} = new mapkit.Map('{$options->id}', {$formattedOptions}); {$options->id}._markers = []; {$formattedMarkers}.forEach(function (marker) { marker.position.unshift(null); const m = new mapkit.MarkerAnnotation( new (mapkit.Coordinate.bind.apply(mapkit.Coordinate, marker.position)), { glyphText: marker.label || '', color: marker.color || '', } ); {$options->id}._markers.push(m); {$options->id}.addAnnotation(m); }); JS; $css = $this->_getCss($options); $this->_js('https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js'); $view->registerJs($initJs, View::POS_END); $view->registerJs($js, View::POS_END); $css && $view->registerCss($css); return '
'; } /** * @param EmbedOptions $options * @param Settings $settings * * @return string * @throws Exception */ private function _embedMapbox (EmbedOptions $options, Settings $settings): string { $view = Craft::$app->getView(); switch ($settings->mapTiles) { default: case MapTiles::MapboxStreets: $type = 'streets-v11'; break; case MapTiles::MapboxOutdoors: $type = 'outdoors-v11'; break; case MapTiles::MapboxLight: $type = 'light-v10'; break; case MapTiles::MapboxDark: $type = 'dark-v10'; break; } $formattedOptions = Json::encode( array_merge( [ 'style' => 'mapbox://styles/mapbox/' . $type, 'zoom' => $options->zoom, ], $options->options, [ 'container' => $options->id, 'center' => array_reverse(array_values($options->getCenter())), ] ), self::JSON_OPTS ); $formattedMarkers = []; foreach ($options->markers as $marker) $formattedMarkers[] = [ 'position' => array_reverse(array_values($marker->getCenter())), // TODO: Add label support 'color' => $marker->color, ]; $formattedMarkers = Json::encode( $formattedMarkers, self::JSON_OPTS ); $initJs = <<id} = new mapboxgl.Map({$formattedOptions}); {$options->id}._markers = []; {$formattedMarkers}.forEach(function (marker) { {$options->id}._markers.push( new mapboxgl.Marker({ color: marker.color }) .setLngLat(marker.position) .addTo({$options->id}) ); }); JS; $css = $this->_getCss($options); $this->_js('https://api.tiles.mapbox.com/mapbox-gl-js/v1.3.1/mapbox-gl.js'); $view->registerCssFile('https://api.tiles.mapbox.com/mapbox-gl-js/v1.3.1/mapbox-gl.css'); $view->registerJs($initJs, View::POS_END); $view->registerJs($js, View::POS_END); $css && $view->registerCss($css); return '
'; } /** * @param EmbedOptions $options * @param Settings $settings * * @return string * @throws InvalidConfigException */ private function _embedHere (EmbedOptions $options, Settings $settings): string { if (!array_key_exists('apiKey', $settings->getMapToken()) || !$settings->getMapToken()['apiKey']) throw new InvalidConfigException('Missing HERE API Key'); $view = Craft::$app->getView(); $markerIcon = $this->_iconSvg(); $formattedOptions = Json::encode( array_merge( [ 'zoom' => $options->zoom, ], $options->options, [ 'center' => $options->getCenter(), 'pixelRatio' => '##PIXELRATIO##', ] ), self::JSON_OPTS ); $formattedOptions = str_replace([ '"##PIXELRATIO##"', ], [ 'window.devicePixelRatio || 1', ], $formattedOptions); switch ($settings->mapTiles) { default: case MapTiles::HereReduced: case MapTiles::HerePedestrian: case MapTiles::HereNormalDay: $type = 'normal.map'; break; case MapTiles::HereTerrain: case MapTiles::HereNormalDayGrey: $type = 'terrain.map'; break; case MapTiles::HereNormalDayTransit: $type = 'normal.transit'; break; case MapTiles::HereSatellite: $type = 'satellite.xbase'; break; case MapTiles::HereHybrid: $type = 'satellite.map'; break; } $formattedMarkers = []; foreach ($options->markers as $marker) $formattedMarkers[] = [ 'position' => $marker->getCenter(), 'label' => $marker->label ?: '', 'color' => $marker->color, ]; $formattedMarkers = Json::encode( $formattedMarkers, self::JSON_OPTS ); $initJs = <<getMapToken()['apiKey']}' }); window.HERE_defaultLayers = HERE_platform.createDefaultLayers(); JS; $js = <<id} = new H.Map( document.getElementById('{$options->id}'), window.HERE_defaultLayers.raster.{$type}, {$formattedOptions} ); {$options->id}._behaviour = new H.mapevents.Behavior(new H.mapevents.MapEvents({$options->id})); {$options->id}._ui = H.ui.UI.createDefault({$options->id}, window.HERE_defaultLayers); {$options->id}._markers = []; {$formattedMarkers}.forEach(function (marker) { const m = new H.map.Marker( marker.position, { icon: new H.map.Icon('{$markerIcon}'.replace('##FILL##', marker.color).replace('##LABEL##', marker.label)), } ); {$options->id}._markers.push(m); {$options->id}.addObject(m); }); window.addEventListener('resize', function () { {$options->id}.getViewPort().resize() }); JS; $css = $this->_getCss($options); $this->_js('https://js.api.here.com/v3/3.1/mapsjs-core.js'); $this->_js('https://js.api.here.com/v3/3.1/mapsjs-service.js'); $this->_js('https://js.api.here.com/v3/3.1/mapsjs-ui.js'); $this->_js('https://js.api.here.com/v3/3.1/mapsjs-mapevents.js'); $view->registerCssFile('https://js.api.here.com/v3/3.1/mapsjs-ui.css'); $view->registerJs($initJs, View::POS_END); $view->registerJs($js, View::POS_END); $css && $view->registerCss($css); return '
'; } /** * @param EmbedOptions $options * @param Settings $settings * * @return string * @throws Exception */ private function _embedDefault (EmbedOptions $options, Settings $settings): string { $view = Craft::$app->getView(); $markerIcon = $this->_iconSvg(); switch ($settings->mapTiles) { default: case MapTiles::Wikimedia: $tiles = 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}##SCALE##'; $attr = '© OpenStreetMap, © Wikimedia'; break; case MapTiles::OpenStreetMap: $tiles = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; $attr = '© OpenStreetMap'; break; case MapTiles::CartoVoyager: case MapTiles::CartoPositron: case MapTiles::CartoDarkMatter: $style = explode('.', $settings->mapTiles)[1]; $tiles = 'https://{s}.basemaps.cartocdn.com/' . $style . '/{z}/{x}/{y}##SCALE##'; $attr = '© OpenStreetMap, © CARTO'; } $formattedOptions = Json::encode($options->options, self::JSON_OPTS); $formattedMarkers = []; foreach ($options->markers as $marker) $formattedMarkers[] = [ 'position' => $marker->getCenter(), 'label' => $marker->label ?: '', 'color' => $marker->color, ]; $formattedMarkers = Json::encode( $formattedMarkers, self::JSON_OPTS ); $center = Json::encode(array_values($options->getCenter()), self::JSON_OPTS); $initJs = <<id} */ const {$options->id} = L.map('{$options->id}', {$formattedOptions}) .setView({$center}, {$options->zoom}); window.LMapTiles({$options->id}); {$options->id}._markers = []; {$formattedMarkers}.forEach(function (marker) { const m = L.marker( marker.position, { icon: window.LMapMarkerIcon(marker) } ); {$options->id}._markers.push(m); {$options->id}.addLayer(m); }); /* End Map: {$options->id} */ JS; $css = $this->_getCss($options); $this->_js( 'https://unpkg.com/leaflet@1.5.1/dist/leaflet.js', [ 'integrity' => 'sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og==', 'crossorigin' => '', ] ); $view->registerCssFile( 'https://unpkg.com/leaflet@1.5.1/dist/leaflet.css', [ 'integrity' => 'sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==', 'crossorigin' => '', ] ); $view->registerJs($initJs, View::POS_END); $view->registerJs($js, View::POS_END); $css && $view->registerCss($css); return '
'; } // Helpers // ========================================================================= /** * @param string $url - The URL to the JS file * @param array $options - An array of options to be used as attributes * @param string $pre - Will link rel="pre${pre}" if not null */ private function _js (string $url, array $options = [], string $pre = 'connect') { $view = Craft::$app->getView(); if ($pre) { $crossOrigin = !$this->_compareUrls( $url, Craft::$app->sites->currentSite->baseUrl ); $view->registerLinkTag( [ 'rel' => 'pre' . $pre, 'href' => $url, 'as' => 'script', 'crossorigin' => $crossOrigin, ] ); } $view->registerScript( '', View::POS_END, array_merge( ['src' => $url], $options ), md5($url) ); } private function _compareUrls ($a, $b): bool { $a = parse_url($a, PHP_URL_HOST); $b = parse_url($b, PHP_URL_HOST); return $this->_trim($a) === $this->_trim($b); } private function _trim ($str): string { if (stripos($str, 'www.') === 0) return substr($str, 4); return $str; } private function _iconSvg () { static $svg; if ($svg) return $svg; $svg = Craft::getAlias('@simplemap/resources/marker.svg'); $svg = file_get_contents($svg); $svg = (new Sanitizer())->sanitize($svg); $svg = preg_replace('/\s*/s', '', $svg); $svg = preg_replace('/.*?<\/title>\s*/is', '', $svg); $svg = preg_replace('/<desc>.*?<\/desc>\s*/is', '', $svg); $svg = preg_replace('/<\?xml.*?\?>/', '', $svg); $svg = preg_replace('/[\r\n]/', '', $svg); $svg = preg_replace('/[\s]{2,}/', '', $svg); return $svg; } private function _getCss (EmbedOptions $options): ?string { if ($options->width === null && $options->height === null) return null; $css = "#{$options->id} {"; if ($options->width !== null) $css .= 'width:' . $options->width . 'px;'; if ($options->height !== null) $css .= 'height:' . $options->height . 'px;'; return $css . '}'; } } ================================================ FILE: src/services/GeoLocationService.php ================================================ <?php /** * Maps for Craft CMS * * @link https://ethercreative.co.uk * @copyright Copyright (c) 2019 Ether Creative */ namespace ether\simplemap\services; use Craft; use craft\base\Component; use craft\helpers\Json; use DateTime; use ether\simplemap\jobs\MaxMindDBDownloadJob; use ether\simplemap\models\Settings; use ether\simplemap\models\UserLocation; use ether\simplemap\SimpleMap; use Exception; use GeoIp2\Database\Reader; use GeoIp2\WebService\Client; /** * Class GeoLocationService * * @author Ether Creative * @package ether\simplemap\services */ class GeoLocationService extends Component { // Consts // ========================================================================= const DB_STORAGE = '@runtime/maps/db'; const None = 'none'; const IpStack = 'ipstack'; const MaxMindLite = 'maxmind-lite'; const MaxMind = 'maxmind'; // Methods // ========================================================================= /** * Lookup the location of the current users IP (or passed IP) * * @param string|null $ip * * @return UserLocation|null * @throws Exception */ public function lookup (string $ip = null): ?UserLocation { if (SimpleMap::v(SimpleMap::EDITION_LITE)) throw new Exception('Sorry, user geolocation is a Maps Pro feature!'); if (!$ip) $ip = Craft::$app->getRequest()->getUserIP(); if (!self::_isValidIp($ip)) { Craft::error('Invalid or not allowed IP address: "' . $ip . '"', 'maps'); return null; } /** @var Settings $settings */ $settings = SimpleMap::getInstance()->getSettings(); if ($cached = $this->_getUserLocationFromCache($ip, $settings)) return $cached; $userLocation = match ($settings->geoLocationService) { self::IpStack => $this->_lookup_IpStack( $settings->getGeoLocationToken(), $ip ), self::MaxMind => $this->_lookup_MaxMind( $settings->getGeoLocationToken(), $ip ), self::MaxMindLite => $this->_lookup_MaxMindLite($ip), default => null, }; if ($userLocation) $this->_cacheUserLocation($userLocation, $settings); return $userLocation; } /** * @throws Exception */ public function redirect () { $settings = SimpleMap::getInstance()->getSettings(); $siteHandle = null; $location = $this->lookup(); if (!$location || empty($settings->geoLocationAutoRedirect)) return; foreach ($settings->geoLocationRedirectMap as $handle => $props) { if ($props === '*') { $siteHandle = $handle; continue; } if (self::_validateProps($location, $props)) { $siteHandle = $handle; break; } } if ($siteHandle === null) return; $site = Craft::$app->getSites()->getSiteByHandle($siteHandle); if ($site->id === Craft::$app->getSites()->getCurrentSite()->id) return; if (!$site) { Craft::error('Unable to find site with handle "' . $siteHandle . '"', 'simplemap'); return; } if (!$site->hasUrls) { Craft::error('Selected site (' . $siteHandle . ') doesn\'t have URLs!', 'simplemap'); return; } $currentBaseUrl = Craft::$app->getSites()->getCurrentSite()->getBaseUrl(); $currentUrl = str_replace( $currentBaseUrl, '', Craft::$app->getRequest()->getAbsoluteUrl() ); if (str_contains($currentUrl, '://')) $currentUrl = str_replace( Craft::$app->getRequest()->getBaseUrl(), '', $currentUrl ); $url = rtrim($site->getBaseUrl(), '/') . '/' . $currentUrl; Craft::$app->getResponse()->redirect($url); } // Public Helpers // ========================================================================= public static function getSelectOptions (): array { return [ self::None => SimpleMap::t('None'), self::IpStack => SimpleMap::t('ipstack'), // self::MaxMindLite => SimpleMap::t('MaxMind (Lite, ~60MB download)'), self::MaxMind => SimpleMap::t('MaxMind'), ]; } // MaxMind DB // ------------------------------------------------------------------------- /** * Check if the database file exists * * @param string $filename * * @return bool */ public static function dbExists (string $filename = 'default.mmdb'): bool { return file_exists( Craft::getAlias(self::DB_STORAGE . DIRECTORY_SEPARATOR . $filename) ); } /** * Should we update the database (is it older than 1 week?) * * @param string $filename * * @return bool * @throws Exception */ public static function dbShouldUpdate (string $filename = 'default.mmdb'): bool { $updated = filemtime( Craft::getAlias(self::DB_STORAGE . DIRECTORY_SEPARATOR . $filename) ); if ($updated === false) return false; return $updated < (new DateTime())->modify('-7 days')->getTimestamp(); } /** * Start the MaxMind DB download job */ public static function dbQueueDownload () { if (Craft::$app->getCache()->get('maps_db_updating')) return; Craft::$app->getCache()->set('maps_db_updating', true); Craft::$app->getQueue()->push(new MaxMindDBDownloadJob()); } public static function purgeDb ($filename = 'default.mmdb') { $file = Craft::getAlias(self::DB_STORAGE . DIRECTORY_SEPARATOR . $filename); if (file_exists($file)) unlink($file); } // Private Helpers // ========================================================================= // Caching // ------------------------------------------------------------------------- /** * @param string $ip * @param Settings $settings * * @return UserLocation|false */ private function _getUserLocationFromCache (string $ip, Settings $settings): bool|UserLocation { if (!$settings->geoLocationCacheDuration) return false; return Craft::$app->getCache()->get( 'maps_ip_' . $ip ); } /** * @param UserLocation $userLocation * @param Settings $settings * * @return bool */ private function _cacheUserLocation (UserLocation $userLocation, Settings $settings): bool { if (!$settings->geoLocationCacheDuration) return true; return Craft::$app->getCache()->set( 'maps_ip_' . $userLocation->ip, $userLocation, $settings->geoLocationCacheDuration ); } // Lookup Services // ------------------------------------------------------------------------- private function _lookup_IpStack ($token, $ip): ?UserLocation { $url = 'http://api.ipstack.com/' . $ip; $url .= '?access_key=' . $token; $url .= '&language=' . Craft::$app->getLocale()->getLanguageID(); $data = self::_client()->get($url)->getBody(); $data = Json::decodeIfJson($data); if (array_key_exists('success', $data) && $data['success'] === false) { Craft::error($data['error']['info'], 'maps'); return null; } $parts = [ 'city' => $data['city'], 'postcode' => $data['zip'], 'state' => $data['region_name'], 'country' => $data['country_name'], ]; return new UserLocation([ 'ip' => $ip, 'lat' => $data['latitude'], 'lng' => $data['longitude'], 'address' => implode(', ', array_filter($parts)), 'countryCode' => $data['country_code'], 'isEU' => $data['location']['is_eu'], 'parts' => $parts, ]); } private function _lookup_MaxMind ($token, $ip): ?UserLocation { $client = new Client( $token['accountId'], $token['licenseKey'], [ Craft::$app->getLocale()->getLanguageID(), 'en' ] ); $record = null; try { $record = $client->city($ip); } catch (Exception $e) { Craft::error($e->getMessage(), 'maps'); return null; } return self::_populateMaxMind($ip, $record); } /** * @param $ip * * @return UserLocation|null * @throws Exception */ private function _lookup_MaxMindLite ($ip): ?UserLocation { if (!self::dbExists()) { // self::dbQueueDownload(); throw new Exception('MaxMind Lite is no longer supported'); } // if (self::dbShouldUpdate()) // self::dbQueueDownload(); Craft::warning('MaxMind Lite is no longer supported and will not receive updates'); try { $reader = new Reader( Craft::getAlias( self::DB_STORAGE . DIRECTORY_SEPARATOR . 'default.mmdb' ) ); $record = $reader->city($ip); } catch (Exception $e) { Craft::dd($e); Craft::error($e->getMessage(), 'maps'); return null; } return self::_populateMaxMind($ip, $record); } // Misc // ------------------------------------------------------------------------- private static function _client () { static $client; if (!$client) $client = Craft::createGuzzleClient(); return $client; } /** * Ensure IP is valid and not private or reserved * * @param string $ip * * @return mixed */ private static function _isValidIp (string $ip): mixed { return filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ); } /** * @param $ip * @param $record * * @return UserLocation */ private static function _populateMaxMind ($ip, $record): UserLocation { $parts = [ 'city' => $record->city->name, 'postcode' => $record->postal->code, 'state' => $record->mostSpecificSubdivision->name, 'country' => $record->country->name, ]; return new UserLocation([ 'ip' => $ip, 'lat' => $record->location->latitude, 'lng' => $record->location->longitude, 'address' => implode(', ', array_filter($parts)), 'countryCode' => $record->country->isoCode, 'isEU' => $record->country->isInEuropeanUnion, 'parts' => $parts, ]); } /** * @param UserLocation $location * @param $props * * @return bool */ private static function _validateProps (UserLocation $location, $props): bool { foreach ($props as $key => $value) { if (is_array($value)) { foreach ($value as $item) if ($location->$key === $item) continue; return false; } if (is_callable($value)) return $value($location->$key); if ($location->$key !== $value) return false; } return true; } } ================================================ FILE: src/services/GeoService.php ================================================ <?php /** * Maps for Craft CMS 3 * * @link https://ethercreative.co.uk * @copyright Copyright (c) 2019 Ether Creative */ namespace ether\simplemap\services; use Craft; use craft\base\Component; use craft\helpers\Json; use ether\simplemap\enums\GeoService as GeoEnum; use ether\simplemap\enums\MapTiles; use ether\simplemap\models\Map; use ether\simplemap\models\Parts; use ether\simplemap\models\PartsLegacy; use ether\simplemap\models\Settings; use ether\simplemap\SimpleMap; use GuzzleHttp\Client; use Mapkit\JWT; use Exception; /** * Class GeoService * * @author Ether Creative * @package ether\simplemap\services */ class GeoService extends Component { // Properties // ========================================================================= public static array $countries = [ 'AF' => 'Afghanistan', 'AX' => 'Åland Islands', 'AL' => 'Albania', 'DZ' => 'Algeria', 'AS' => 'American Samoa', 'AD' => 'Andorra', 'AO' => 'Angola', 'AI' => 'Anguilla', 'AQ' => 'Antarctica', 'AG' => 'Antigua and Barbuda', 'AR' => 'Argentina', 'AM' => 'Armenia', 'AW' => 'Aruba', 'AU' => 'Australia', 'AT' => 'Austria', 'AZ' => 'Azerbaijan', 'BS' => 'Bahamas', 'BH' => 'Bahrain', 'BD' => 'Bangladesh', 'BB' => 'Barbados', 'BY' => 'Belarus', 'BE' => 'Belgium', 'BZ' => 'Belize', 'BJ' => 'Benin', 'BM' => 'Bermuda', 'BT' => 'Bhutan', 'BO' => 'Bolivia, Plurinational State of', 'BQ' => 'Bonaire, Sint Eustatius and Saba', 'BA' => 'Bosnia and Herzegovina', 'BW' => 'Botswana', 'BV' => 'Bouvet Island', 'BR' => 'Brazil', 'IO' => 'British Indian Ocean Territory', 'BN' => 'Brunei Darussalam', 'BG' => 'Bulgaria', 'BF' => 'Burkina Faso', 'BI' => 'Burundi', 'KH' => 'Cambodia', 'CM' => 'Cameroon', 'CA' => 'Canada', 'CV' => 'Cape Verde', 'KY' => 'Cayman Islands', 'CF' => 'Central African Republic', 'TD' => 'Chad', 'CL' => 'Chile', 'CN' => 'China', 'CX' => 'Christmas Island', 'CC' => 'Cocos (Keeling) Islands', 'CO' => 'Colombia', 'KM' => 'Comoros', 'CG' => 'Congo', 'CD' => 'Congo, the Democratic Republic of the', 'CK' => 'Cook Islands', 'CR' => 'Costa Rica', 'CI' => 'Côte d\'Ivoire', 'HR' => 'Croatia', 'CU' => 'Cuba', 'CW' => 'Curaçao', 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'DK' => 'Denmark', 'DJ' => 'Djibouti', 'DM' => 'Dominica', 'DO' => 'Dominican Republic', 'EC' => 'Ecuador', 'EG' => 'Egypt', 'SV' => 'El Salvador', 'GQ' => 'Equatorial Guinea', 'ER' => 'Eritrea', 'EE' => 'Estonia', 'ET' => 'Ethiopia', 'FK' => 'Falkland Islands (Malvinas)', 'FO' => 'Faroe Islands', 'FJ' => 'Fiji', 'FI' => 'Finland', 'FR' => 'France', 'GF' => 'French Guiana', 'PF' => 'French Polynesia', 'TF' => 'French Southern Territories', 'GA' => 'Gabon', 'GM' => 'Gambia', 'GE' => 'Georgia', 'DE' => 'Germany', 'GH' => 'Ghana', 'GI' => 'Gibraltar', 'GR' => 'Greece', 'GL' => 'Greenland', 'GD' => 'Grenada', 'GP' => 'Guadeloupe', 'GU' => 'Guam', 'GT' => 'Guatemala', 'GG' => 'Guernsey', 'GN' => 'Guinea', 'GW' => 'Guinea-Bissau', 'GY' => 'Guyana', 'HT' => 'Haiti', 'HM' => 'Heard Island and McDonald Islands', 'VA' => 'Holy See (Vatican City State)', 'HN' => 'Honduras', 'HK' => 'Hong Kong', 'HU' => 'Hungary', 'IS' => 'Iceland', 'IN' => 'India', 'ID' => 'Indonesia', 'IR' => 'Iran, Islamic Republic of', 'IQ' => 'Iraq', 'IE' => 'Ireland', 'IM' => 'Isle of Man', 'IL' => 'Israel', 'IT' => 'Italy', 'JM' => 'Jamaica', 'JP' => 'Japan', 'JE' => 'Jersey', 'JO' => 'Jordan', 'KZ' => 'Kazakhstan', 'KE' => 'Kenya', 'KI' => 'Kiribati', 'KP' => 'Korea, Democratic People\'s Republic of', 'KR' => 'Korea, Republic of', 'KW' => 'Kuwait', 'KG' => 'Kyrgyzstan', 'LA' => 'Lao People\'s Democratic Republic', 'LV' => 'Latvia', 'LB' => 'Lebanon', 'LS' => 'Lesotho', 'LR' => 'Liberia', 'LY' => 'Libya', 'LI' => 'Liechtenstein', 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'MO' => 'Macao', 'MK' => 'Macedonia, the Former Yugoslav Republic of', 'MG' => 'Madagascar', 'MW' => 'Malawi', 'MY' => 'Malaysia', 'MV' => 'Maldives', 'ML' => 'Mali', 'MT' => 'Malta', 'MH' => 'Marshall Islands', 'MQ' => 'Martinique', 'MR' => 'Mauritania', 'MU' => 'Mauritius', 'YT' => 'Mayotte', 'MX' => 'Mexico', 'FM' => 'Micronesia, Federated States of', 'MD' => 'Moldova, Republic of', 'MC' => 'Monaco', 'MN' => 'Mongolia', 'ME' => 'Montenegro', 'MS' => 'Montserrat', 'MA' => 'Morocco', 'MZ' => 'Mozambique', 'MM' => 'Myanmar', 'NA' => 'Namibia', 'NR' => 'Nauru', 'NP' => 'Nepal', 'NL' => 'Netherlands', 'NC' => 'New Caledonia', 'NZ' => 'New Zealand', 'NI' => 'Nicaragua', 'NE' => 'Niger', 'NG' => 'Nigeria', 'NU' => 'Niue', 'NF' => 'Norfolk Island', 'MP' => 'Northern Mariana Islands', 'NO' => 'Norway', 'OM' => 'Oman', 'PK' => 'Pakistan', 'PW' => 'Palau', 'PS' => 'Palestine, State of', 'PA' => 'Panama', 'PG' => 'Papua New Guinea', 'PY' => 'Paraguay', 'PE' => 'Peru', 'PH' => 'Philippines', 'PN' => 'Pitcairn', 'PL' => 'Poland', 'PT' => 'Portugal', 'PR' => 'Puerto Rico', 'QA' => 'Qatar', 'RE' => 'Réunion', 'RO' => 'Romania', 'RU' => 'Russian Federation', 'RW' => 'Rwanda', 'BL' => 'Saint Barthélemy', 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', 'KN' => 'Saint Kitts and Nevis', 'LC' => 'Saint Lucia', 'MF' => 'Saint Martin (French part)', 'PM' => 'Saint Pierre and Miquelon', 'VC' => 'Saint Vincent and the Grenadines', 'WS' => 'Samoa', 'SM' => 'San Marino', 'ST' => 'Sao Tome and Principe', 'SA' => 'Saudi Arabia', 'SN' => 'Senegal', 'RS' => 'Serbia', 'SC' => 'Seychelles', 'SL' => 'Sierra Leone', 'SG' => 'Singapore', 'SX' => 'Sint Maarten (Dutch part)', 'SK' => 'Slovakia', 'SI' => 'Slovenia', 'SB' => 'Solomon Islands', 'SO' => 'Somalia', 'ZA' => 'South Africa', 'GS' => 'South Georgia and the South Sandwich Islands', 'SS' => 'South Sudan', 'ES' => 'Spain', 'LK' => 'Sri Lanka', 'SD' => 'Sudan', 'SR' => 'Suriname', 'SJ' => 'Svalbard and Jan Mayen', 'SZ' => 'Swaziland', 'SE' => 'Sweden', 'CH' => 'Switzerland', 'SY' => 'Syrian Arab Republic', 'TW' => 'Taiwan, Province of China', 'TJ' => 'Tajikistan', 'TZ' => 'Tanzania, United Republic of', 'TH' => 'Thailand', 'TL' => 'Timor-Leste', 'TG' => 'Togo', 'TK' => 'Tokelau', 'TO' => 'Tonga', 'TT' => 'Trinidad and Tobago', 'TN' => 'Tunisia', 'TR' => 'Turkey', 'TM' => 'Turkmenistan', 'TC' => 'Turks and Caicos Islands', 'TV' => 'Tuvalu', 'UG' => 'Uganda', 'UA' => 'Ukraine', 'AE' => 'United Arab Emirates', 'GB' => 'United Kingdom', 'US' => 'United States', 'UM' => 'United States Minor Outlying Islands', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan', 'VU' => 'Vanuatu', 'VE' => 'Venezuela, Bolivarian Republic of', 'VN' => 'Viet Nam', 'VG' => 'Virgin Islands, British', 'VI' => 'Virgin Islands, U.S.', 'WF' => 'Wallis and Futuna', 'EH' => 'Western Sahara', 'YE' => 'Yemen', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe', ]; public static array $countriesIso3 = [ 'AD' => 'AND', 'AE' => 'ARE', 'AF' => 'AFG', 'AG' => 'ATG', 'AI' => 'AIA', 'AL' => 'ALB', 'AM' => 'ARM', 'AO' => 'AGO', 'AQ' => 'ATA', 'AR' => 'ARG', 'AS' => 'ASM', 'AT' => 'AUT', 'AU' => 'AUS', 'AW' => 'ABW', 'AX' => 'ALA', 'AZ' => 'AZE', 'BA' => 'BIH', 'BB' => 'BRB', 'BD' => 'BGD', 'BE' => 'BEL', 'BF' => 'BFA', 'BG' => 'BGR', 'BH' => 'BHR', 'BI' => 'BDI', 'BJ' => 'BEN', 'BL' => 'BLM', 'BM' => 'BMU', 'BN' => 'BRN', 'BO' => 'BOL', 'BQ' => 'BES', 'BR' => 'BRA', 'BS' => 'BHS', 'BT' => 'BTN', 'BV' => 'BVT', 'BW' => 'BWA', 'BY' => 'BLR', 'BZ' => 'BLZ', 'CA' => 'CAN', 'CC' => 'CCK', 'CD' => 'COD', 'CF' => 'CAF', 'CG' => 'COG', 'CH' => 'CHE', 'CI' => 'CIV', 'CK' => 'COK', 'CL' => 'CHL', 'CM' => 'CMR', 'CN' => 'CHN', 'CO' => 'COL', 'CR' => 'CRI', 'CU' => 'CUB', 'CV' => 'CPV', 'CW' => 'CUW', 'CX' => 'CXR', 'CY' => 'CYP', 'CZ' => 'CZE', 'DE' => 'DEU', 'DJ' => 'DJI', 'DK' => 'DNK', 'DM' => 'DMA', 'DO' => 'DOM', 'DZ' => 'DZA', 'EC' => 'ECU', 'EE' => 'EST', 'EG' => 'EGY', 'EH' => 'ESH', 'ER' => 'ERI', 'ES' => 'ESP', 'ET' => 'ETH', 'FI' => 'FIN', 'FJ' => 'FJI', 'FK' => 'FLK', 'FM' => 'FSM', 'FO' => 'FRO', 'FR' => 'FRA', 'GA' => 'GAB', 'GB' => 'GBR', 'GD' => 'GRD', 'GE' => 'GEO', 'GF' => 'GUF', 'GG' => 'GGY', 'GH' => 'GHA', 'GI' => 'GIB', 'GL' => 'GRL', 'GM' => 'GMB', 'GN' => 'GIN', 'GP' => 'GLP', 'GQ' => 'GNQ', 'GR' => 'GRC', 'GS' => 'SGS', 'GT' => 'GTM', 'GU' => 'GUM', 'GW' => 'GNB', 'GY' => 'GUY', 'HK' => 'HKG', 'HM' => 'HMD', 'HN' => 'HND', 'HR' => 'HRV', 'HT' => 'HTI', 'HU' => 'HUN', 'ID' => 'IDN', 'IE' => 'IRL', 'IL' => 'ISR', 'IM' => 'IMN', 'IN' => 'IND', 'IO' => 'IOT', 'IQ' => 'IRQ', 'IR' => 'IRN', 'IS' => 'ISL', 'IT' => 'ITA', 'JE' => 'JEY', 'JM' => 'JAM', 'JO' => 'JOR', 'JP' => 'JPN', 'KE' => 'KEN', 'KG' => 'KGZ', 'KH' => 'KHM', 'KI' => 'KIR', 'KM' => 'COM', 'KN' => 'KNA', 'KP' => 'PRK', 'KR' => 'KOR', 'KW' => 'KWT', 'KY' => 'CYM', 'KZ' => 'KAZ', 'LA' => 'LAO', 'LB' => 'LBN', 'LC' => 'LCA', 'LI' => 'LIE', 'LK' => 'LKA', 'LR' => 'LBR', 'LS' => 'LSO', 'LT' => 'LTU', 'LU' => 'LUX', 'LV' => 'LVA', 'LY' => 'LBY', 'MA' => 'MAR', 'MC' => 'MCO', 'MD' => 'MDA', 'ME' => 'MNE', 'MF' => 'MAF', 'MG' => 'MDG', 'MH' => 'MHL', 'MK' => 'MKD', 'ML' => 'MLI', 'MM' => 'MMR', 'MN' => 'MNG', 'MO' => 'MAC', 'MP' => 'MNP', 'MQ' => 'MTQ', 'MR' => 'MRT', 'MS' => 'MSR', 'MT' => 'MLT', 'MU' => 'MUS', 'MV' => 'MDV', 'MW' => 'MWI', 'MX' => 'MEX', 'MY' => 'MYS', 'MZ' => 'MOZ', 'NA' => 'NAM', 'NC' => 'NCL', 'NE' => 'NER', 'NF' => 'NFK', 'NG' => 'NGA', 'NI' => 'NIC', 'NL' => 'NLD', 'NO' => 'NOR', 'NP' => 'NPL', 'NR' => 'NRU', 'NU' => 'NIU', 'NZ' => 'NZL', 'OM' => 'OMN', 'PA' => 'PAN', 'PE' => 'PER', 'PF' => 'PYF', 'PG' => 'PNG', 'PH' => 'PHL', 'PK' => 'PAK', 'PL' => 'POL', 'PM' => 'SPM', 'PN' => 'PCN', 'PR' => 'PRI', 'PS' => 'PSE', 'PT' => 'PRT', 'PW' => 'PLW', 'PY' => 'PRY', 'QA' => 'QAT', 'RE' => 'REU', 'RO' => 'ROU', 'RS' => 'SRB', 'RU' => 'RUS', 'RW' => 'RWA', 'SA' => 'SAU', 'SB' => 'SLB', 'SC' => 'SYC', 'SD' => 'SDN', 'SE' => 'SWE', 'SG' => 'SGP', 'SH' => 'SHN', 'SI' => 'SVN', 'SJ' => 'SJM', 'SK' => 'SVK', 'SL' => 'SLE', 'SM' => 'SMR', 'SN' => 'SEN', 'SO' => 'SOM', 'SR' => 'SUR', 'SS' => 'SSD', 'ST' => 'STP', 'SV' => 'SLV', 'SX' => 'SXM', 'SY' => 'SYR', 'SZ' => 'SWZ', 'TC' => 'TCA', 'TD' => 'TCD', 'TF' => 'ATF', 'TG' => 'TGO', 'TH' => 'THA', 'TJ' => 'TJK', 'TK' => 'TKL', 'TL' => 'TLS', 'TM' => 'TKM', 'TN' => 'TUN', 'TO' => 'TON', 'TR' => 'TUR', 'TT' => 'TTO', 'TV' => 'TUV', 'TW' => 'TWN', 'TZ' => 'TZA', 'UA' => 'UKR', 'UG' => 'UGA', 'UM' => 'UMI', 'US' => 'USA', 'UY' => 'URY', 'UZ' => 'UZB', 'VA' => 'VAT', 'VC' => 'VCT', 'VE' => 'VEN', 'VG' => 'VGB', 'VI' => 'VIR', 'VN' => 'VNM', 'VU' => 'VUT', 'WF' => 'WLF', 'WS' => 'WSM', 'XK' => 'XKX', 'YE' => 'YEM', 'YT' => 'MYT', 'ZA' => 'ZAF', 'ZM' => 'ZMB', 'ZW' => 'ZWE', ]; // Methods // ========================================================================= /** * Parses the token based off the given service * * @param array|string $token * @param string $service * * @return bool|array|string */ public static function getToken (array|string $token, string $service): bool|array|string { switch ($service) { case GeoEnum::AppleMapKit: case MapTiles::MapKitStandard: case MapTiles::MapKitMutedStandard: case MapTiles::MapKitSatellite: case MapTiles::MapKitHybrid: return JWT::getToken( trim($token['privateKey']), trim($token['keyId']), trim($token['teamId']) ); default: return $token; } } /** * Find the lat/lng for the given address * * @param string $address * @param string|null $country * * @return array|null * @throws Exception */ public static function latLngFromAddress (string $address, string $country = null): ?array { /** @var Settings $settings */ $settings = SimpleMap::getInstance()->getSettings(); $token = static::getToken($settings->getGeoToken(), $settings->geoService); return match ($settings->geoService) { GeoEnum::Here => static::_latLngFromAddress_Here( $token, $address, $country ), GeoEnum::GoogleMaps => static::_latLngFromAddress_Google( $token, $address, $country ), GeoEnum::Mapbox => static::_latLngFromAddress_Mapbox( $token, $address, $country ), GeoEnum::Nominatim => static::_latLngFromAddress_Nominatim( $address, $country ), default => throw new Exception( 'Unknown geo-coding service: ' . $settings->geoService ), }; } /** * Find an address from the given lat/lng * * @param float $lat * @param float $lng * * @return array|null - Returns the address and associated parts * @throws Exception */ public static function addressFromLatLng (float $lat, float $lng): ?array { /** @var Settings $settings */ $settings = SimpleMap::getInstance()->getSettings(); $token = static::getToken($settings->getGeoToken(), $settings->geoService); return match ($settings->geoService) { GeoEnum::Here => static::_addressFromLatLng_Here( $token, $lat, $lng ), GeoEnum::GoogleMaps => static::_addressFromLatLng_Google( $token, $lat, $lng ), GeoEnum::Mapbox => static::_addressFromLatLng_Mapbox( $token, $lat, $lng ), GeoEnum::Nominatim => static::_addressFromLatLng_Nominatim( $lat, $lng ), default => throw new Exception( 'Unknown geo-coding service: ' . $settings->geoService ), }; } /** * Normalize the given distance unit * * @param string $unit * * @return string */ public static function normalizeDistance (string $unit): string { if ($unit === 'miles') $unit = 'mi'; else if ($unit === 'kilometers') $unit = 'km'; else if (!in_array($unit, ['mi', 'km'])) $unit = 'km'; return $unit; } /** * Will normalize the given location to a lat/lng array * * @param mixed $location * @param string|null $country * * @return array * @throws Exception */ public static function normalizeLocation (mixed $location, string $country = null): array { if (is_string($location)) $location = self::latLngFromAddress($location, $country); else if ($location instanceof Map) $location = ['lat' => $location->lat, 'lng' => $location->lng]; else if (!is_array($location) || !isset($location['lat'], $location['lng'])) $location = []; if (!$location) { $location = []; } return $location; } // Private Methods // ========================================================================= // Lat/Lng from Address // ------------------------------------------------------------------------- private static function _latLngFromAddress_Here ($token, $address, $country): ?array { $url = 'https://geocoder.api.here.com/6.2/geocode.json'; $url .= '?app_id=' . $token['appId']; $url .= '&app_code=' . $token['appCode']; $url .= '&language=' . Craft::$app->locale->getLanguageID(); $url .= '&searchtext=' . rawurlencode($address); if ($country !== null) { if (static::_validateCountryCode($country)) $url .= '&country=' . rawurlencode(strtoupper($country)); else $url .= rawurlencode(', ' . $country); } $data = (string) static::_client()->get($url)->getBody(); $data = Json::decodeIfJson($data); if (!is_array($data) || empty($data['Response']['View'])) return null; $pos = $data['Response']['View'][0]['Result'][0]['Location']['DisplayPosition']; return [ 'lat' => $pos['Latitude'], 'lng' => $pos['Longitude'], ]; } private static function _latLngFromAddress_Google ($token, $address, $country): ?array { $url = 'https://maps.googleapis.com/maps/api/geocode/json'; $url .= '?address=' . rawurlencode($address); $url .= '&language=' . Craft::$app->locale->getLanguageID(); if ($country !== null) { if (static::_validateCountryCode($country)) $url .= '&components=country:' . rawurlencode(strtoupper($country)); else $url .= rawurlencode(', ' . $country); } $url .= '&key=' . $token; $data = (string) static::_client()->get($url)->getBody(); $data = Json::decodeIfJson($data); if (!is_array($data) || empty($data['results'])) return null; return [ 'lat' => $data['results'][0]['geometry']['location']['lat'], 'lng' => $data['results'][0]['geometry']['location']['lng'], ]; } private static function _latLngFromAddress_Mapbox ($token, $address, $country): ?array { $url = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; $url .= rawurlencode($address) . '.json?limit=1'; $url .= '&language=' . Craft::$app->locale->getLanguageID(); $url .= '&access_token=' . $token; if ($country !== null) { if (static::_validateCountryCode($country)) $url .= '&country=' . rawurlencode(strtolower($country)); else $url = str_replace('.json', rawurlencode(', ' . $country) . '.json', $url); } $referer = Craft::$app->getRequest()->getIsConsoleRequest() ? Craft::getAlias('@web') : Craft::$app->urlManager->getHostInfo(); $data = (string) static::_client()->get($url, [ 'headers' => [ 'referer' => $referer, ] ])->getBody(); $data = Json::decodeIfJson($data); if (!is_array($data) || empty($data['features'])) return null; return [ 'lat' => $data['features'][0]['center'][1], 'lng' => $data['features'][0]['center'][0], ]; } private static function _latLngFromAddress_Nominatim ($address, $country): ?array { $url = 'https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1'; $url .= '&accept-language=' . Craft::$app->locale->getLanguageID(); $url .= '&q=' . rawurlencode($address); if ($country !== null) { if (static::_validateCountryCode($country)) $url .= '&countrycode=' . rawurlencode(strtolower($country)); else $url .= '&country=' . rawurlencode($country); } $data = (string) static::_client()->get($url)->getBody(); $data = Json::decodeIfJson($data); if (!is_array($data) || empty($data)) return null; return [ 'lat' => $data[0]['lat'], 'lng' => $data[0]['lon'], ]; } // Address from Lat/Lng // ------------------------------------------------------------------------- private static function _addressFromLatLng_Here ($token, $lat, $lng): ?array { $url = 'https://reverse.geocoder.api.here.com/6.2/reversegeocode.json'; $url .= '?app_id=' . $token['appId']; $url .= '&app_code=' . $token['appCode']; $url .= '&language=' . Craft::$app->locale->getLanguageID(); $url .= '&mode=retrieveAddresses&limit=1&jsonattributes=1'; $url .= '&prox=' . rawurlencode($lat) . ',' . rawurldecode($lng) . ',1'; $data = (string) static::_client()->get($url)->getBody(); $data = Json::decodeIfJson($data); if (!is_array($data) || empty($data['Response']['View'])) return null; $pos = $data['Response']['View'][0]['Result'][0]['Location']; return [ 'address' => $pos['label'], 'parts' => new Parts($pos, GeoEnum::Here), ]; } private static function _addressFromLatLng_Google ($token, $lat, $lng): ?array { $url = 'https://maps.googleapis.com/maps/api/geocode/json'; $url .= '?latlng=' . rawurlencode($lat) . ',' . rawurldecode($lng); $url .= '&language=' . Craft::$app->locale->getLanguageID(); $url .= '&key=' . $token; $data = (string) static::_client()->get($url)->getBody(); $data = Json::decodeIfJson($data); if (!is_array($data) || empty($data['results'])) return null; $pos = $data['results'][0]; return [ 'address' => $pos['formatted_address'], 'parts' => new PartsLegacy($pos['address_components']), ]; } private static function _addressFromLatLng_Mapbox ($token, $lat, $lng): ?array { $url = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; $url .= rawurlencode($lng) . ',' . rawurldecode($lat) . '.json?limit=1'; $url .= '&types=address,country,postcode,place,locality,district,neighborhood'; $url .= '&language=' . Craft::$app->locale->getLanguageID(); $url .= '&access_token=' . $token; $data = (string) static::_client()->get($url)->getBody(); $data = Json::decodeIfJson($data); if (!is_array($data) || empty($data['features'])) return null; $feature = $data['features'][0]; return [ 'address' => $feature['place_name'], 'parts' => new Parts($feature, GeoEnum::Mapbox), ]; } private static function _addressFromLatLng_Nominatim ($lat, $lng): ?array { $url = 'https://nominatim.openstreetmap.org/reverse?format=jsonv2&limit=1&addressdetails=1'; $url .= '&accept-language=' . Craft::$app->locale->getLanguageID(); $url .= '&lat=' . rawurlencode($lat) . '&lon=' . rawurldecode($lng); $data = (string) static::_client()->get($url)->getBody(); $data = Json::decodeIfJson($data); if (!is_array($data) || empty($data) || array_key_exists('error', $data)) return null; return [ 'address' => $data['display_name'], 'parts' => new Parts( array_merge( $data['address'], ['type' => $data['type']] ), GeoEnum::Nominatim ), ]; } // Helpers // ========================================================================= private static function _client () { static $client; if (!$client) $client = Craft::createGuzzleClient(); return $client; } private static function _validateCountryCode (string $code): bool { return in_array(strtoupper($code), array_keys(static::$countries)); } } ================================================ FILE: src/services/MapService.php ================================================ <?php /** * Maps for Craft CMS 3 * * @link https://ethercreative.co.uk * @copyright Copyright (c) 2019 Ether Creative */ namespace ether\simplemap\services; use Craft; use craft\base\Component; use craft\base\Element; use craft\base\ElementInterface; use craft\elements\db\ElementQuery; use craft\elements\db\ElementQueryInterface; use craft\errors\InvalidFieldException; use craft\helpers\Json; use ether\simplemap\models\Map; use ether\simplemap\fields\MapField; use ether\simplemap\records\Map as MapRecord; use ether\simplemap\SimpleMap; use Exception; use function Arrayy\array_first; /** * Class MapService * * @author Ether Creative * @package ether\simplemap\services */ class MapService extends Component { // Properties // ========================================================================= private $_location; private $_distance; // Methods // ========================================================================= /** * @param MapField $field * @param ElementInterface $owner * * @return bool * @throws InvalidFieldException */ public function validateField (MapField $field, ElementInterface $owner): bool { /** @var Map $map */ $map = $owner->getFieldValue($field->handle); $valid = $map->validate(); foreach ($map->getErrors() as $error) $owner->addError($field->handle, $error[0]); return $valid; } /** * @param MapField $field * @param ElementInterface $owner * * @throws InvalidFieldException */ public function saveField (MapField $field, ElementInterface $owner) { /** @var Map $map */ $map = $owner->getFieldValue($field->handle); $map->fieldId = $field->id; $map->ownerId = $owner->id; $map->ownerSiteId = $owner->siteId; $record = MapRecord::findOne([ 'ownerId' => $map->ownerId, 'ownerSiteId' => $map->ownerSiteId, 'fieldId' => $map->fieldId, ]); if ($record) $map->id = $record->id; $this->saveRecord($map, !$map->id); } /** * @param Map $map * @param $isNew * * @throws Exception */ public function saveRecord (Map $map, $isNew) { $record = null; if (!$isNew) { $record = MapRecord::findOne($map->id); if (!$record) throw new Exception('Invalid map ID: ' . $map->id); } if ($record === null) { $record = new MapRecord(); if ($map->id) $record->id = $map->id; $record->ownerId = $map->ownerId; $record->ownerSiteId = $map->ownerSiteId; $record->fieldId = $map->fieldId; } $record->lat = $map->lat; $record->lng = $map->lng; $record->save(false); } /** * Returns the distance from the search origin (if one exists) * * @param Map $map * * @return float|int|null */ public function getDistance (Map $map): float|int|null { if (!$this->_location || !$this->_distance) return null; $originLat = (float) $this->_location['lat']; $originLng = (float) $this->_location['lng']; $targetLat = (float) $map->lat; $targetLng = (float) $map->lng; return ( $this->_distance * rad2deg( acos( cos(deg2rad($originLat)) * cos(deg2rad($targetLat)) * cos(deg2rad($originLng) - deg2rad($targetLng)) + sin(deg2rad($originLat)) * sin(deg2rad($targetLat)) ) ) ); } /** * @param ElementQueryInterface $query * @param mixed $value * @param MapField $field * * @throws Exception */ public function modifyElementsQuery (ElementQueryInterface $query, mixed $value, MapField $field) { if (is_null($value)) return; // Work-around for Craft built-in GraphQL not supporting custom // arguments for fields: // If it's an array with a 0 key, that means it's likely to be a `[QueryArgument]` if (is_array($value) && array_key_exists(0, $value)) $value = $value[0]; // If it's a string, check to see if it's JSON and decode it. if (is_string($value)) $value = Json::decodeIfJson($value); // End work-around /** @var ElementQuery $query */ $table = MapRecord::TableName; $alias = MapRecord::TableNameClean . '_' . $field->handle; $on = [ 'and', '[[elements.id]] = [[' . $alias . '.ownerId]]', '[[elements.dateDeleted]] IS NULL', '[[elements_sites.siteId]] = [[' . $alias . '.ownerSiteId]]', '[[' . $alias . '.fieldId]] = ' . $field->id, ]; $query->subQuery->join('LEFT JOIN', $table . ' ' . $alias, $on); if ($value === ':empty:') { $query->subQuery->andWhere([ '[[' . $alias . '.lat]]' => null, ]); return; } else if ($value === ':notempty:' || $value === 'not :empty:') { $query->subQuery->andWhere([ 'not', ['[[' . $alias . '.lat]]' => null], ]); return; } $oldOrderBy = null; $search = false; if (!is_array($query->orderBy)) { $oldOrderBy = $query->orderBy; $query->orderBy = []; } // Coordinate CraftQL support if (array_key_exists('coordinate', $value)) $value['location'] = $value['coordinate']; if (array_key_exists('location', $value)) $search = $this->_searchLocation($query, $value, $alias); if (array_key_exists('distance', $query->orderBy)) $this->_replaceOrderBy($query, $search); if (empty($query->orderBy)) $query->orderBy = $oldOrderBy; } /** * Populates any missing location data * * @param Map $map * @param MapField $field * * @throws Exception */ public function populateMissingData (Map $map, MapField $field) { $settings = SimpleMap::getInstance()->getSettings(); // Missing zoom if (!$map->zoom) $map->zoom = $field->zoom; // Skip the rest if populate missing is disabled if ($settings->disablePopulateMissingFieldData) return; $postcode = is_array($map->parts) ? @$map->parts['postcode'] : $map->parts->postcode; // Missing Lat / Lng if (!($map->lat && $map->lng) && !empty($map->address ?: $postcode)) { $latLng = GeoService::latLngFromAddress($map->address ?: $postcode); if ($latLng) { $map->lat = $latLng['lat']; $map->lng = $latLng['lng']; } } // Missing address / parts if ((!$map->address || $map->address === $postcode) && ($map->lat && $map->lng)) { $loc = GeoService::addressFromLatLng($map->lat, $map->lng); if ($loc) { $map->address = $loc['address']; $map->parts = array_merge( array_filter((array) $loc['parts']), array_filter((array) $map->parts) ); } } // Missing what3words if ($settings->isW3WEnabled() && empty($map->what3words)) $map->what3words = What3WordsService::convertLatLngToW3W($map->lat, $map->lng); } // Private Methods // ========================================================================= /** * Filters the query by location. * * Returns either `false` if we can't filter by location, or the location * search string if we can. * * @param ElementQuery $query * @param mixed $value * @param string $table * * @return bool|string * @throws Exception */ private function _searchLocation (ElementQuery $query, mixed $value, string $table): bool|string { $location = $value['location']; $country = $value['country'] ?? null; $radius = $value['radius'] ?? 50.0; $unit = $value['unit'] ?? 'km'; // Normalize location $location = GeoService::normalizeLocation($location, $country); // If we don't have a location, reduce the search radius to 0 if (empty($location)) { $location = ['lat' => 0, 'lng' => 0]; $radius = 0; } $lat = $location['lat']; $lng = $location['lng']; // Normalize radius if (!is_numeric($radius)) $radius = (float) $radius; if (!is_numeric($radius)) $radius = 50.0; // Normalize unit $unit = GeoService::normalizeDistance($unit); // Base Distance $distance = $unit === 'km' ? '111.045' : '69.0'; // Store for populating search result distance $this->_location = $location; $this->_distance = (float) $distance; // Search Query $search = str_replace(["\r", "\n", "\t"], '', "( $distance * DEGREES( ACOS( COS(RADIANS($lat)) * COS(RADIANS([[$table.lat]])) * COS(RADIANS($lng) - RADIANS([[$table.lng]])) + SIN(RADIANS($lat)) * SIN(RADIANS([[$table.lat]])) ) ) )"); // Restrict the results $restrict = [ 'and', [ 'and', "[[$table.lat]] >= $lat - ($radius / $distance)", "[[$table.lat]] <= $lat + ($radius / $distance)", ], [ 'and', "[[$table.lng]] >= $lng - ($radius / ($distance * COS(RADIANS($lat))))", "[[$table.lng]] <= $lng + ($radius / ($distance * COS(RADIANS($lat))))", ] ]; // Filter the query $query ->subQuery ->addSelect($search . ' as [[distance]]') ->andWhere($restrict) ->andWhere([ 'not', ['[[' . $table . '.lat]]' => null], ]); if (Craft::$app->getDb()->driverName === 'pgsql') $query->subQuery->andWhere($search . ' <= ' . $radius); else $query->subQuery->andHaving('[[distance]] <= ' . $radius); return '[[distance]]'; } /** * Will replace the distance search with the correct query if available, * or otherwise remove it. * * @param ElementQuery $query * @param bool $search */ private function _replaceOrderBy (ElementQuery $query, string $search = "") { $nextOrder = []; foreach ((array) $query->orderBy as $order => $sort) { if ($order === 'distance' && !empty($search)) $nextOrder[$search] = $sort; elseif ($order !== 'distance') $nextOrder[$order] = $sort; } $query->subQuery->orderBy($nextOrder); $query->orderBy($nextOrder); } } ================================================ FILE: src/services/StaticService.php ================================================ <?php /** * Maps for Craft CMS * * @link https://ethercreative.co.uk * @copyright Copyright (c) 2019 Ether Creative */ namespace ether\simplemap\services; use Craft; use craft\base\Component; use craft\helpers\Json; use craft\helpers\UrlHelper; use ether\simplemap\enums\MapTiles; use ether\simplemap\models\Marker; use ether\simplemap\models\Settings; use ether\simplemap\models\StaticOptions; use ether\simplemap\SimpleMap; use ether\simplemap\utilities\StaticMap; use Exception; /** * Class StaticService * * @author Ether Creative * @package ether\simplemap\services */ class StaticService extends Component { /** * @param array $options * * @return string * @throws Exception */ public function generate (array $options = []): string { if (SimpleMap::v(SimpleMap::EDITION_LITE)) return 'Sorry, static maps are a Maps Pro feature!'; $options = new StaticOptions($options); /** @var Settings $settings */ $settings = SimpleMap::getInstance()->getSettings(); switch ($settings->mapTiles) { case MapTiles::GoogleHybrid: case MapTiles::GoogleRoadmap: case MapTiles::GoogleTerrain: return $this->_generateGoogle($options, $settings); case MapTiles::MapKitHybrid: case MapTiles::MapKitMutedStandard: case MapTiles::MapKitSatellite: case MapTiles::MapKitStandard: return $this->_generateApple($options, $settings); case MapTiles::MapboxDark: case MapTiles::MapboxLight: case MapTiles::MapboxOutdoors: case MapTiles::MapboxStreets: return $this->_generateMapbox($options, $settings); case MapTiles::HereHybrid: case MapTiles::HereNormalDay: case MapTiles::HereNormalDayGrey: case MapTiles::HereNormalDayTransit: case MapTiles::HerePedestrian: case MapTiles::HereReduced: case MapTiles::HereSatellite: case MapTiles::HereTerrain: return $this->_generateHere($options, $settings); default: return $this->_generateDefault($options); } } // Generators // ========================================================================= /** * @param StaticOptions $options * @param Settings $settings * * @return string * @throws Exception */ private function _generateGoogle (StaticOptions $options, Settings $settings): string { $params = [ 'center' => implode(',', $options->getCenter()), 'zoom' => $options->zoom, 'size' => $options->getSize(), 'scale' => $options->scale, 'language' => Craft::$app->getLocale()->getLanguageID(), 'region' => $this->_getTld(), 'key' => GeoService::getToken( $settings->getMapToken(), $settings->mapTiles ), ]; $markersString = ''; if (!empty($options->markers)) { $markers = []; /** @var Marker $marker */ foreach ($options->markers as $marker) { $m = [ 'color:' . str_replace('#', '0x', $marker->color), ]; if ($marker->label !== null) $m[] = 'label:' . strtoupper($marker->label); $m[] = $marker->getLocation(); $markers[] = implode('|', $m); } $markersString = '&markers=' . implode('&markers=', $markers); } $params['maptype'] = match ($settings->mapTiles) { MapTiles::GoogleTerrain => 'terrain', MapTiles::GoogleRoadmap => 'roadmap', MapTiles::GoogleHybrid => 'hybrid', }; return 'https://maps.googleapis.com/maps/api/staticmap?' . http_build_query($params) . $markersString; } /** * @param StaticOptions $options * @param Settings $settings * * @return string * @throws Exception */ private function _generateApple (StaticOptions $options, Settings $settings): string { $params = [ 'center' => implode(',', $options->getCenter()), 'z' => $options->zoom, 'size' => $options->getSize(), 'scale' => $options->scale, 'lang' => Craft::$app->getLocale()->getLanguageID(), 'teamId' => $settings->getMapToken()['teamId'], 'keyId' => $settings->getMapToken()['keyId'], ]; if (!empty($options->markers)) { $params['annotations'] = []; foreach ($options->markers as $marker) { $params['annotations'][] = [ 'color' => str_replace('#', '', $marker->color), 'glyphText' => $marker->label, 'point' => $marker->getLocation(true), ]; } $params['annotations'] = Json::encode($params['annotations']); } switch ($settings->mapTiles) { case MapTiles::MapKitStandard: $params['type'] = 'standard'; break; case MapTiles::MapKitSatellite: $params['type'] = 'satellite'; break; case MapTiles::MapKitMutedStandard: $params['type'] = 'mutedStandard'; break; case MapTiles::MapKitHybrid: $params['type'] = 'hybrid'; break; } $path = '/api/v1/snapshot?' . http_build_query($params); openssl_sign($path, $signature, $settings->getMapToken()['privateKey'], OPENSSL_ALGO_SHA256); $signature = $this->_encode($signature); return 'https://snapshot.apple-mapkit.com' . $path . '&signature=' . $signature; } /** * @param StaticOptions $options * @param Settings $settings * * @return string * @throws Exception */ private function _generateMapbox (StaticOptions $options, Settings $settings): string { $url = 'https://api.mapbox.com/styles/v1/mapbox/'; switch ($settings->mapTiles) { case MapTiles::MapboxStreets: $url .= 'streets-v11'; break; case MapTiles::MapboxOutdoors: $url .= 'outdoors-v11'; break; case MapTiles::MapboxLight: $url .= 'light-v10'; break; case MapTiles::MapboxDark: $url .= 'dark-v10'; break; } $url .= '/static/'; if (!empty($options->markers)) { $markers = []; $i = 0; foreach ($options->markers as $marker) { $m = 'pin-l-'; $m .= strtolower($marker->label ?: ++$i); $m .= '+' . str_replace('#', '', $marker->color); $m .= '(' . implode(',', array_reverse(explode(',', $marker->getLocation(true)))) . ')'; $markers[] = $m; } $url .= implode(',', $markers) . '/'; } $center = $options->getCenter(); $url .= $center['lng'] . ','; $url .= $center['lat'] . ','; $url .= $options->zoom . ',0,0'; $url .= '/' . $options->getSize(); if ($options->scale > 1) $url .= '@2x'; return $url . '?access_token=' . $settings->getMapToken(); } /** * @param StaticOptions $options * @param Settings $settings * * @return string * @throws Exception */ private function _generateHere (StaticOptions $options, Settings $settings): string { $params = [ 'app_id' => $settings->getMapToken()['appId'], 'app_code' => $settings->getMapToken()['appCode'], 'nodot' => true, 'c' => implode(',', $options->getCenter()), 'z' => $options->zoom, 'w' => $options->width * $options->scale, 'h' => $options->height * $options->scale, ]; switch ($settings->mapTiles) { case MapTiles::HereHybrid: $params['t'] = 3; break; case MapTiles::HereNormalDay: $params['t'] = 0; break; case MapTiles::HereNormalDayGrey: $params['t'] = 5; break; case MapTiles::HereNormalDayTransit: $params['t'] = 4; break; case MapTiles::HereReduced: $params['t'] = 6; break; case MapTiles::HerePedestrian: $params['t'] = 13; break; case MapTiles::HereSatellite: $params['t'] = 1; break; case MapTiles::HereTerrain: $params['t'] = 2; break; } if (!empty($options->markers)) { $i = 0; foreach ($options->markers as $marker) { $m = [$marker->getLocation(true)]; $m[] = str_replace('#', '', $marker->color); $m[] = StaticMap::getLabelColour($marker->color); $m[] = 18; $m[] = $marker->label ?: ++$i; $params['poix' . ($i - 1)] = implode(';', $m); } } return 'https://image.maps.api.here.com/mia/1.6/mapview?' . http_build_query($params); } /** * @param StaticOptions $options * * @return string * @throws Exception */ private function _generateDefault (StaticOptions $options): string { $center = $options->getCenter(); return UrlHelper::actionUrl( 'simplemap/static', [ 'lat' => $center['lat'], 'lng' => $center['lng'], 'zoom' => $options->zoom, 'width' => $options->width, 'height' => $options->height, 'scale' => $options->scale, 'markers' => urlencode(implode(';', $options->markers)), 'csrf' => Craft::$app->getRequest()->getCsrfToken(), ] ); } // Helpers // ========================================================================= private function _getTld (): array { $url = 'http://' . $_SERVER['SERVER_NAME']; return explode(".", parse_url($url, PHP_URL_HOST)); } private function _encode ($data): string { $encoded = strtr(base64_encode($data), '+/', '-_'); return rtrim($encoded, '='); } } ================================================ FILE: src/services/What3WordsService.php ================================================ <?php /** * Maps for Craft CMS * * @link https://ethercreative.co.uk * @copyright Copyright (c) 2020 Ether Creative */ namespace ether\simplemap\services; use ether\simplemap\SimpleMap; use What3words\Geocoder\Geocoder; /** * Class What3WordsService * * @author Ether Creative * @package ether\simplemap\services */ class What3WordsService { public static function convertLatLngToW3W ($lat, $lng) { return self::_geocoder()->convertTo3wa($lat, $lng)['words'] ?? false; } private static function _geocoder () { static $geocoder; if ($geocoder) return $geocoder; return $geocoder = new Geocoder(SimpleMap::getInstance()->getSettings()->getW3WToken()); } } ================================================ FILE: src/templates/_feedme-mapping.twig ================================================ {# ------------------------ #} {# Available Variables #} {# ------------------------ #} {# Attributes: #} {# type, name, handle, instructions, attribute, default, feed, feedData #} {# ------------------------ #} {# Fields: #} {# name, handle, instructions, feed, feedData, field, fieldClass #} {# ------------------------ #} {% import 'feed-me/_macros' as feedMeMacro %} {% import '_includes/forms' as forms %} {# Special case when inside another complex field (Matrix) #} {% if parentPath is defined %} {% set prefixPath = parentPath %} {% else %} {% set prefixPath = [handle] %} {% endif %} {% set classes = ['complex-field'] %} <tr class="complex-field complex-field-header"> <td class="col-field" colspan="3"> <div class="field"> <div class="heading"> <label class="">{{ name }}</label> </div> <div class="additional-mapping-fields"> {% namespace 'fieldMapping[' ~ prefixPath | join('][') ~ ']' %} <input type="text" name="field" value="{{ className(field) }}"> {% endnamespace %} </div> </div> </td> </tr> {% set simpleMapSubfields = { lat: 'Latitude'|t('simplemap'), lng: 'Longitude'|t('simplemap'), zoom: 'Zoom'|t('simplemap'), address: 'Full Address'|t('simplemap'), 'parts.number': 'Number'|t('simplemap'), 'parts.address': 'Address'|t('simplemap'), 'parts.city': 'City'|t('simplemap'), 'parts.postcode': 'Postcode'|t('simplemap'), 'parts.county': 'County'|t('simplemap'), 'parts.state': 'State'|t('simplemap'), 'parts.country': 'Country'|t('simplemap'), } %} {% for key, col in simpleMapSubfields %} {% set splitKey = '.' in key %} {% set subKey = null %} {% if splitKey %} {% set key = key|split('.') %} {% set subKey = key[1] %} {% set key = key[0] %} {% endif %} {% set nameLabel = col %} {% set instructionsHandle = handle ~ '[' ~ key ~ ']' ~ (subKey ? '[' ~ subKey ~ ']') %} {% set path = prefixPath | merge ([ 'fields', key, subKey ]|filter) %} {% set default = default ?? { type: 'text', } %} {% embed 'feed-me/_includes/fields/_base' %} {% block additionalFieldSettings %} {% endblock %} {% block fieldSettings %} {% endblock %} {% endembed %} {% endfor %} ================================================ FILE: src/templates/field-settings.twig ================================================ {% import '_includes/forms' as forms %} {{ forms.field({ label: 'Initial Location'|t('simplemap'), instructions: 'The initial location and zoom that will show in the map field'|t('simplemap'), }, map) }} {{ forms.lightswitchField({ label: 'Hide Search'|t('simplemap'), instructions: 'Hide the location search field'|t('simplemap'), id: 'hideSearch', name: 'hideSearch', on: field.hideSearch }) }} {{ forms.lightswitchField({ label: 'Hide Map'|t('simplemap'), instructions: 'Hide the map'|t('simplemap'), id: 'hideMap', name: 'hideMap', on: field.hideMap }) }} {{ forms.lightswitchField({ label: 'Hide Address'|t('simplemap'), instructions: 'Hide the address fields'|t('simplemap'), id: 'hideAddress', name: 'hideAddress', on: field.hideAddress }) }} {{ forms.lightswitchField({ label: 'Show Latitude / Longitude'|t('simplemap'), instructions: 'Show the latitude / longitude fields'|t('simplemap'), id: 'showLatLng', name: 'showLatLng', on: field.showLatLng }) }} {{ forms.lightswitchField({ label: 'Show Current Location'|t('simplemap'), instructions: 'Show a button to centre the map on the users current location'|t('simplemap'), id: 'showCurrentLocation', name: 'showCurrentLocation', on: field.showCurrentLocation }) }} {{ forms.selectField({ label: 'Field Size'|t('simplemap'), instructions: 'Choose the size of the field to display'|t('simplemap'), id: 'size', name: 'size', options: { 'normal': 'Normal'|t('simplemap'), 'mini': 'Mini'|t('simplemap'), }, value: field.size, }) }} {{ forms.selectField({ label: 'Preferred Country'|t('simplemap'), instructions: 'When searching for a location, results in this country will take precedence. Be aware that some services will show results ONLY within this country.'|t('simplemap'), id: 'country', name: 'country', options: countries, value: field.country, }) }} {{ forms.textField({ label: 'Min Zoom'|t('simplemap'), instructions: 'The minimum level the user can zoom the map out to.'|t('simplemap'), id: 'minZoom', name: 'minZoom', type: 'number', value: field.minZoom, min: 0, max: 18, step: 0.01, }) }} {{ forms.textField({ label: 'Max Zoom'|t('simplemap'), instructions: 'The maximum level the user can zoom the map in to.'|t('simplemap'), id: 'maxZoom', name: 'maxZoom', type: 'number', value: field.maxZoom, min: 0, max: 18, step: 0.01, }) }} {% if settings.isW3WEnabled() %} {{ forms.lightswitchField({ label: 'Enable what3words grid'|t('simplemap'), instructions: 'Will enable the what3words grid overlay when zoomed in above 17'|t('simplemap'), id: 'showW3WGrid', name: 'showW3WGrid', on: field.showW3WGrid }) }} {{ forms.lightswitchField({ label: 'Show what3words field'|t('simplemap'), instructions: 'Show the what3words field for the selected location'|t('simplemap'), id: 'showW3WField', name: 'showW3WField', on: field.showW3WField }) }} {% endif %} ================================================ FILE: src/templates/settings.twig ================================================ {% extends '_layouts/cp' %} {% set title = 'Settings'|t('app') %} {% set showHeader = false %} {% set fullPageForm = true %} {% do craft.app.view.registerAssetBundle('craft\\web\\assets\\vue\\VueAsset') %} {#{% dd settings %}#} {% macro selectLoop (options, value) %} {% set hasOptgroups = false %} {% for key, option in options %} {% if option.optgroup is defined %} {% if hasOptgroups %} </optgroup> {% else %} {% set hasOptgroups = true %} {% endif %} <optgroup label="{{ option.optgroup }}"> {% else %} {% set optionLabel = (option.label is defined ? option.label : option) %} {% set optionValue = (option.value is defined ? option.value : key) %} {% set optionDisabled = (option.disabled is defined ? option.disabled : false) %} <option value="{{ optionValue }}"{% if (optionValue~'') is same as (value~'') %} selected{% endif %}{% if optionDisabled %} disabled{% endif %}> {{- optionLabel -}} </option> {% endif %} {% endfor %} {% if hasOptgroups %} </optgroup> {% endif %} {% endmacro %} {% macro label (label, instructions, children) %} <label class="maps-label"> <strong>{{ label|t('simplemap') }}</strong> <span>{{ instructions|t('simplemap') }}</span> {{ children }} </label> {% endmacro %} {% macro tokenValue (token, key) -%} {%- if token is iterable and key in token|keys -%} {{- token[key] -}} {%- endif -%} {%- endmacro %} {% from _self import selectLoop, label, tokenValue %} {% block content %} <div v-cloak> <input type="hidden" name="action" value="plugins/save-plugin-settings"> <input type="hidden" name="pluginHandle" value="simplemap"> {{ redirectInput('maps/settings') }} <header class="maps-header"> <h1> {{ svg('@simplemap/icon.svg') }} <span>{{ 'Maps'|t('simplemap') }}</span> </h1> <button> Save </button> </header> <section class="maps-map"> {% for key, label in mapTileOptions %} {% if key != '0' and key != '1' %} {% set x1 = craft.app.assetManager.getPublishedUrl('@simplemapimages/' ~ (key|replace({'.':'-','/':'-'})) ~ '-1x.jpg', true) %} {% set x2 = craft.app.assetManager.getPublishedUrl('@simplemapimages/' ~ (key|replace({'.':'-','/':'-'})) ~ '.jpg', true) %} <image-fade-on-load src="{{ x1 }}" srcset="{{ x1 }} 1x, {{ x2 }} 2x" alt="{{ label is iterable ? label.label : label }}" :show="mapTiles === '{{ key }}'" ></image-fade-on-load> {% endif %} {% endfor %} {% namespace 'settings' %} <label class="maps-card maps-style"> <span>{{ 'Select your map style'|t('simplemap') }}</span> <select name="mapTiles" v-model="mapTiles"> {{ selectLoop(mapTileOptions, settings.mapTiles) }} </select> </label> <div class="maps-card"> {% set mapTokenIterable = settings.mapToken is iterable %} <div v-if="mapTilesSimple && requiresMapToken"> {% set children %} <craft-autosuggest name="mapToken" value="{{ not mapTokenIterable ? settings.mapToken }}" ></craft-autosuggest> {% endset %} {{ label('Map Token', 'Add the API key for map tiles service you are using.', children) }} </div> <div v-if="mapTiles.indexOf('mapkit') > -1"> {% set children %} <textarea name="mapToken[privateKey]" rows="6" >{{ tokenValue(settings.mapToken, 'privateKey') }}</textarea> {% endset %} {{ label('Private Key', 'Paste the contents of your private key files below (supports env variables).', children) }} {% set children %} <craft-autosuggest name="mapToken[keyId]" value="{{ tokenValue(settings.mapToken, 'keyId') }}" ></craft-autosuggest> {% endset %} {{ label('Key ID', 'The ID of the key associated with your private key.', children) }} {% set children %} <craft-autosuggest name="mapToken[teamId]" value="{{ tokenValue(settings.mapToken, 'teamId') }}" ></craft-autosuggest> {% endset %} {{ label('Team ID', 'The team ID that created the key ID and private key.', children) }} </div> <div v-if="mapTiles.indexOf('here') > -1"> {% set children %} <craft-autosuggest name="mapToken[appId]" value="{{ tokenValue(settings.mapToken, 'appId') }}" ></craft-autosuggest> {% endset %} {{ label('App ID', 'Your Here app ID.', children) }} {% set children %} <craft-autosuggest name="mapToken[appCode]" value="{{ tokenValue(settings.mapToken, 'appCode') }}" ></craft-autosuggest> {% endset %} {{ label('App Code', 'Your Here app code.', children) }} {% set children %} <craft-autosuggest name="mapToken[apiKey]" value="{{ tokenValue(settings.mapToken, 'apiKey') }}" ></craft-autosuggest> {% endset %} {{ label('API Key', 'Your Here API Key (only required for front-end embed maps).', children) }} </div> </div> </section> <section class="maps-settings"> <div class="maps-card"> <h2>Geocoding</h2> {% set children %} <div class="maps-select"> <select name="geoService" v-model="geoService"> {{ selectLoop(geoServiceOptions, settings.geoService) }} </select> </div> {% endset %} {{ label('Geo Service', 'Select the service to be used for Geocoding.', children) }} {% set geoTokenIterable = settings.geoToken is iterable %} <div v-if="geoServiceSimple && requiresGeoToken"> {% set children %} <craft-autosuggest name="geoToken" value="{{ not geoTokenIterable ? settings.geoToken }}" ></craft-autosuggest> {% endset %} {{ label('Geo Token', 'Add the API key for the geocoding service.', children) }} </div> <div v-if="geoService.indexOf('mapkit') > -1"> <div class="field"> <div class="warning"> <p style="display:inline-block;vertical-align:top"> <strong>{{ 'Notice'|t('simplemap') }}</strong><br> {{ 'MapKit does not support individual address parts.'|t('simplemap') }} </p> </div> </div> {% set children %} <textarea name="geoToken[privateKey]" rows="6" >{{ tokenValue(settings.geoToken, 'privateKey') }}</textarea> {% endset %} {{ label('Private Key', 'Paste the contents of your private key files below (supports env variables).', children) }} {% set children %} <craft-autosuggest name="geoToken[keyId]" value="{{ tokenValue(settings.geoToken, 'keyId') }}" ></craft-autosuggest> {% endset %} {{ label('Key ID', 'The ID of the key associated with your private key.', children) }} {% set children %} <craft-autosuggest name="geoToken[teamId]" value="{{ tokenValue(settings.geoToken, 'teamId') }}" ></craft-autosuggest> {% endset %} {{ label('Team ID', 'The team ID that created the key ID and private key.', children) }} </div> <div v-if="geoService.indexOf('here') > -1"> {% set children %} <craft-autosuggest name="geoToken[appId]" value="{{ tokenValue(settings.geoToken, 'appId') }}" ></craft-autosuggest> {% endset %} {{ label('App ID', 'Your Here app ID.', children) }} {% set children %} <craft-autosuggest name="geoToken[appCode]" value="{{ tokenValue(settings.geoToken, 'appCode') }}" ></craft-autosuggest> {% endset %} {{ label('App Code', 'Your Here app code.', children) }} </div> </div> <div class="maps-card maps-help"> <h2>{{ 'Getting API Keys'|t('simplemap') }}</h2> <a href="https://cloud.google.com/maps-platform/#get-started" target="_blank" rel="nofollow" :class="{active:~mapTiles.indexOf('google')||~geoService.indexOf('google')}"> {{ svg('@simplemap/web/assets/svgs/google.svg') }} <span> <em>{{ 'Google Maps'|t('simplemap') }}</em> {{ 'You will need to enable the **Maps JavaScript API** and **Places API** for if using Google for the map tiles, and the **Places API** and **Geocoding API** if you are using it for the Geo service.'|md|striptags('<strong>')|raw }} </span> </a> <a href="https://docs.mapbox.com/help/how-mapbox-works/access-tokens/" target="_blank" rel="nofollow" :class="{active:~mapTiles.indexOf('mapbox')||~geoService.indexOf('mapbox')}"> {{ svg('@simplemap/web/assets/svgs/mapbox.svg') }} <span> <em>{{ 'Mapbox'|t('simplemap') }}</em> {{ 'You can use the same key for both map tiles and geo service, no configuration needed!'|t('simplemap') }} </span> </a> <a href="https://developer.apple.com/documentation/mapkitjs/setting_up_mapkit_js" target="_blank" rel="nofollow" :class="{active:~mapTiles.indexOf('mapkit')||~geoService.indexOf('mapkit')}"> {{ svg('@simplemap/web/assets/svgs/apple.svg') }} <span> <em>{{ 'Apple MapKit'|t('simplemap') }}</em> {{ 'We currently only support Apple MapKit for map tiles only.'|t('simplemap') }} </span> </a> <a href="https://developer.here.com/" target="_blank" rel="nofollow" :class="{active:~mapTiles.indexOf('here')||~geoService.indexOf('here')}"> {{ svg('@simplemap/web/assets/svgs/here.svg') }} <span> <em>{{ 'Here'|t('simplemap') }}</em> {{ 'You can use the same key for both map tiles and geo service, no configuration needed!'|t('simplemap') }} </span> </a> </div> <hr> <div class="maps-card {{ isLite ? 'disabled' }}"> <h2> IP Geolocation <span class="maps-pro">Pro</span> </h2> {% set children %} <div class="maps-select"> <select name="geoLocationService" v-model="geoLocationService"> {{ selectLoop(geoLocationOptions, settings.geoLocationService) }} </select> </div> {% endset %} {{ label('Geolocation Service', 'Select the service to be used for Geolocating users.', children) }} {% set geoLocationTokenIterable = settings.geoLocationToken is iterable %} <div v-if="geoLocationSimple && requiresGeoLocationToken"> {% set children %} <craft-autosuggest name="geoLocationToken" value="{{ not geoLocationTokenIterable ? settings.geoLocationToken }}" ></craft-autosuggest> {% endset %} {{ label('Geolocation Token', 'Add the API key for the geolocation service.', children) }} </div> <div v-if="requiresGeoLocationToken && geoLocationService.indexOf('maxmind') > -1"> {% set children %} <craft-autosuggest name="geoLocationToken[accountId]" value="{{ tokenValue(settings.geoLocationToken, 'accountId') }}" ></craft-autosuggest> {% endset %} {{ label('Account ID', 'Your MaxMind account ID.', children) }} {% set children %} <craft-autosuggest name="geoLocationToken[licenseKey]" value="{{ tokenValue(settings.geoLocationToken, 'licenseKey') }}" ></craft-autosuggest> {% endset %} {{ label('License Key', 'Your MaxMind license key.', children) }} </div> </div> <div class="maps-card maps-help"> <h2>{{ 'Getting Geolocation API Keys'|t('simplemap') }}</h2> <a href="https://ipstack.com/product" target="_blank" rel="nofollow" :class="{active:~geoLocationService.indexOf('ipstack')}"> {{ svg('@simplemap/web/assets/svgs/ipstack.svg') }} <span> <em>{{ 'ipstack'|t('simplemap') }}</em> {{ 'ipstack offer free and paid-for versions of their API.'|raw }} </span> </a> <a href="https://www.maxmind.com/en/geoip2-precision-services" target="_blank" rel="nofollow" :class="{active:~geoLocationService.indexOf('maxmind')}"> {{ svg('@simplemap/web/assets/svgs/maxmind.svg') }} <span> <em>{{ 'MaxMind'|t('simplemap') }}</em> {{ 'MaxMind offer free lookup database that must be stored locally, and a more accurate paid-for version of their API.'|raw }} </span> </a> </div> <hr> <div class="maps-card {{ isLite ? 'disabled' }}"> <h2> {{ 'what3words'|t('simplemap') }} <span class="maps-pro">Pro</span> </h2> {% set children %} <div class="maps-select"> <select name="w3wEnabled" v-model="w3wEnabled"> {{ selectLoop([ { label: 'Yes', value: '1' }, { label: 'No', value: '' }, ], settings.w3wEnabled ? '1' : '') }} </select> </div> {% endset %} {{ label('Enable what3words Integration', '', children) }} <div v-if="requiresW3WToken"> {% set children %} <craft-autosuggest name="w3wToken" value="{{ settings.w3wToken }}" ></craft-autosuggest> {% endset %} {{ label('what3words Token', 'Your what3words API key.', children) }} </div> </div> <div class="maps-card maps-help"> <h2>{{ 'Getting a what3words API Key'|t('simplemap') }}</h2> <a href="https://developer.what3words.com/public-api" target="_blank" rel="nofollow" class="active"> {{ svg('@simplemap/web/assets/svgs/w3w.svg') }} <span> <em>{{ 'what3words'|t('simplemap') }}</em> {{ 'what3words offer a free public API key.'|raw }} </span> </a> </div> </section> {% endnamespace %} </div> {% endblock %} {% do craft.app.view.registerAssetBundle('craft\\web\\assets\\vue\\VueAsset') %} {# Note: these are separate purely as a workaround for issues with PHPStorms syntax highlighting :( #} {% js %} window.__mapsData = { mapTiles: '{{ settings.mapTiles }}', geoService: '{{ settings.geoService }}', geoLocationService: '{{ settings.geoLocationService }}', w3wEnabled: '{{ settings.w3wEnabled ? 1 }}', }; window.__mapsCraftAutosuggestSuggestions = {{ craft.cp.getEnvSuggestions()|json_encode|raw }}; {% endjs %} {% js %} const CraftAutosuggest = { props: ['name', 'value'], delimiters: ['%{', '}'], template: `<div class="autosuggest-container"> <vue-autosuggest :suggestions="filteredOptions" :get-suggestion-value="getSuggestionValue" :input-props="inputProps" :limit="limit" @selected="onSelected" @focus="updateFilteredOptions" @blur="onBlur" @input="onInputChange" v-model="inputProps.initialValue" > <template slot-scope="{suggestion}"> %{suggestion.item.name || suggestion.item} <span v-if="suggestion.item.hint" class="light">– %{suggestion.item.hint}</span> </template> </vue-autosuggest> </div>`, data () { return { query: '', selected: '', filteredOptions: [], suggestions: window.__mapsCraftAutosuggestSuggestions || [], limit: 5, inputProps: { initialValue: this.value, name: this.name, }, }; }, methods: { onInputChange(q) { this.query = (q || '').toLowerCase(); this.updateFilteredOptions(); }, updateFilteredOptions() { if (this.query === '') { this.filteredOptions = this.suggestions; return; } var filtered = []; var i, j, sectionFilter, item, name; var that = this; for (i = 0; i < this.suggestions.length; i++) { sectionFilter = []; for (j = 0; j < this.suggestions[i].data.length; j++) { item = this.suggestions[i].data[j]; if ( (item.name || item).toLowerCase().indexOf(this.query) !== -1 || (item.hint && item.hint.toLowerCase().indexOf(this.query) !== -1) ) { sectionFilter.push(item.name ? item : {name: item}); } } if (sectionFilter.length) { sectionFilter.sort(function(a, b) { var scoreA = that.scoreItem(a, this.query); var scoreB = that.scoreItem(b, this.query); if (scoreA === scoreB) { return 0; } return scoreA < scoreB ? 1 : -1; }); filtered.push({ label: this.suggestions[i].label || null, data: sectionFilter.slice(0, this.limit) }); } } this.filteredOptions = filtered; }, scoreItem(item) { var score = 0; if (item.name.toLowerCase().indexOf(this.query) !== -1) { score += 100 + this.query.length / item.name.length; } if (item.hint && item.hint.toLowerCase().indexOf(this.query) !== -1) { score += this.query.length / item.hint.length; } return score; }, onSelected(option) { if (!option) { return; } this.selected = option.item; // Bring focus back to the input if they selected an alias if (option.item.name[0] == '@') { var input = this.$el.querySelector('input'); input.focus(); input.selectionStart = input.selectionEnd = input.value.length; } }, getSuggestionValue(suggestion) { return suggestion.item.name || suggestion.item; }, onBlur(e) { // Clear out the autosuggestions if the focus has shifted to a new element if (e.relatedTarget) { this.filteredOptions = []; } }, }, }; const ImageFadeOnLoad = { props: ['src', 'srcset', 'alt', 'show'], template: `<transition name="fade"> <img :src="src" :srcset="srcset" :alt="alt" loading="lazy" @load="onLoaded" v-show="loaded && show" /> </transition>`, data () { return { loaded: false }; }, methods: { onLoaded () { this.loaded = true; }, }, }; new Vue({ el: '#content', components: { 'craft-autosuggest': CraftAutosuggest, 'image-fade-on-load': ImageFadeOnLoad, }, data () { return window.__mapsData; }, computed: { requiresMapToken () { return !( this.mapTiles.indexOf('wikimedia') > -1 || this.mapTiles.indexOf('openstreetmap') > -1 || this.mapTiles.indexOf('carto') > -1 ); }, mapTilesSimple () { return ( this.mapTiles.indexOf('mapkit') === -1 && this.mapTiles.indexOf('here') === -1 ); }, requiresGeoToken () { return !( this.geoService.indexOf('nominatim') > -1 ); }, geoServiceSimple () { return ( this.geoService.indexOf('mapkit') === -1 && this.geoService.indexOf('here') === -1 ); }, requiresGeoLocationToken () { return ( this.geoLocationService !== 'none' && this.geoLocationService.indexOf('lite') === -1 ); }, geoLocationSimple () { return ( this.geoLocationService.indexOf('maxmind') === -1 ); }, requiresW3WToken () { return this.w3wEnabled; }, } }); {% endjs %} {% css %} :root { --x-pad: 30px; --y-pad: 30px; } @media only screen and (max-width: 767px) { :root { --x-pad: 12px; } } hr { display: block; width: 100%; margin: 0 50px 30px; margin: 0 50px var(--y-pad, 30px); border-top: 1px solid #D2DBE1; opacity: 0.5; } [v-cloak] { display: none; } .fade-enter-active, .fade-leave-active { transition: opacity .5s; } .fade-enter, .fade-leave-to { opacity: 0; } #content { padding: 0 !important; background-color: #EEF5FA !important; border: none !important; } .maps-header { display: flex; align-items: center; justify-content: space-between; height: 90px; padding: 0 30px; padding: 0 var(--x-pad, 30px); } .maps-header h1 { display: flex; align-items: center; font-size: 0; margin-bottom: 0; } .maps-header svg { position: relative; width: 30px; height: 30px; vertical-align: bottom; margin-right: 10px; } .maps-header span { font-size: 32px; font-weight: 200; } .maps-header button { display: inline-block; padding: 10px 22px; color: #fff; font-size: 14px; letter-spacing: 0; font-weight: 500; appearance: none; -moz-appearance: none; -webkit-appearance: none; background-color: #EF2F30; border: none; border-radius: 5px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.20); cursor: pointer; transition: background-color 0.1s ease, box-shadow 0.1s ease, transform 0.1s ease; } .maps-header button:hover { background-color: #C82C2D; } .maps-header button:active { box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.20); transform: translateY(1px); } .maps-card { background-color: #fff; box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.10); border-radius: 5px; } .maps-card h2 { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0; padding: 25px 30px; color: #29323D; font-size: 20px; font-weight: normal; letter-spacing: 0; border-bottom: 1px solid #D2DBE1; } .maps-pro { display: inline-block; padding: 5px 10px 5px 12px; color: #F22C26; font-size: 12px; font-weight: 800; letter-spacing: 1px; line-height: normal; text-transform: uppercase; text-align: center; background-color: rgba(242, 44, 38, 0.20); border-radius: 18px; } .maps-card.disabled { opacity: 1; } .maps-card.disabled .maps-label { opacity: 0.25; pointer-events: none; } .maps-map { position: relative; z-index: 2; padding: 30px; padding: var(--y-pad, 30px) var(--x-pad, 30px); min-height: 561px; background-color: #e4edf6; } @media only screen and (max-width: 767px) { .maps-map { padding: 30px 30px 250px; padding: var(--y-pad, 30px) var(--x-pad, 30px) 250px; } } .maps-map img { position: absolute; z-index: -2; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; } .maps-map .maps-card { max-width: 538px; } .maps-map .maps-card:not(:last-child) { margin-bottom: 10px; } .maps-style { position: relative; display: block; } .maps-style:before, .maps-style:after { position: absolute; top: 50%; right: 21px; content: ''; border: 4px solid transparent; border-top: none; border-bottom: 7px solid #29323D; opacity: 0.5; pointer-events: none; } .maps-style:before { transform: translateY(-10px); } .maps-style:after { transform: translateY(6px) rotate(180deg); } .maps-style span { position: absolute; top: 14px; left: 20px; color: #29323D; font-size: 13px; letter-spacing: 0; pointer-events: none; opacity: 0.4; } .maps-style select { width: 100%; height: 80px; padding: 32px 40px 13px 20px; font-size: 24px; appearance: none; -moz-appearance: none; -webkit-appearance: none; background: none; border: none; } .maps-label { display: block; padding: 20px; line-height: normal; } .maps-label:not(:last-child) { border-bottom: 1px solid #D2DBE1; } .maps-label strong { display: block; margin-bottom: 2px; color: #29323D; font-size: 12px; font-weight: 600; letter-spacing: 0; opacity: 0.7; } .maps-label span { display: block; color: #29323D; font-size: 12px; font-weight: 300; letter-spacing: 0; opacity: 0.4; } .maps-label input, .maps-label textarea, .maps-label select { width: calc(100% + 40px); margin: 7px -20px 0; color: #29323D; font-size: 16px; letter-spacing: 0; text-indent: 20px; appearance: none; -moz-appearance: none; -webkit-appearance: none; background: none; border: none; resize: none; } .maps-label textarea, .maps-label select { padding: 0 20px; text-indent: 0; } .maps-label textarea { width: 100%; } .maps-select { position: relative; display: block; } .maps-select:before, .maps-select:after { position: absolute; top: 50%; right: 0; content: ''; border: 3px solid transparent; border-top: none; border-bottom: 5px solid #29323D; opacity: 0.5; pointer-events: none; } .maps-select:before { transform: translateY(-4px); } .maps-select:after { transform: translateY(6px) rotate(180deg); } .maps-settings { position: relative; z-index: 3; display: flex; flex-wrap: wrap; align-items: flex-start; width: 750px; margin: -65px auto 0; padding: 30px; padding: var(--y-pad, 30px) var(--x-pad, 30px); } .maps-settings .maps-card { width: 100%; margin-bottom: 30px; } .maps-help { background-color: rgba(210, 219, 225, 0.2); border: 1px solid #D2DBE1; box-shadow: none; } .maps-help a { display: flex; align-items: center; padding: 12px 30px; color: #29323D; font-size: 12px; line-height: 1.5em; opacity: 0.5; transition: opacity 0.15s ease; } .maps-help a:hover, .maps-help a.active { text-decoration: none; opacity: 1; } .maps-help a em { display: block; margin-bottom: 5px; font-size: 16px; font-weight: 500; font-style: normal; } .maps-help h2 + a { padding-top: 25px; } .maps-help a:last-child { padding-bottom: 24px; } .maps-help svg { width: 30px; height: 30px; margin-right: 20px; } .maps-help span { flex-shrink: 9999; } {% endcss %} ================================================ FILE: src/translations/en/simplemap.php ================================================ <?php /** * SimpleMap for Craft CMS * * @link https://ethercreative.co.uk * @copyright Copyright (c) 2019 Ether Creative */ return [ // Field // ========================================================================= 'Map' => 'Map', 'Geo-Coding' => 'Geo-Coding', 'Search for a location' => 'Search for a location', 'Clear address' => 'Clear address', 'No address selected' => 'No address selected', 'Full Address' => 'Full Address', 'Name / Number' => 'Name / Number', 'Street Address' => 'Street Address', 'Town / City' => 'Town / City', 'Postcode' => 'Postcode', 'County' => 'County', 'State' => 'State', 'Country' => 'Country', 'Latitude' => 'Latitude', 'Longitude' => 'Longitude', 'Zoom In' => 'Zoom In', 'Zoom Out' => 'Zoom Out', 'Center on Marker' => 'Center on Marker', 'Current Location' => 'Current Location', // Field: Settings // ------------------------------------------------------------------------- 'Initial Location' => 'Initial Location', 'The initial location and zoom that will show in the map field' => 'The initial location and zoom that will show in the map field', 'Hide Search' => 'Hide Search', 'Hide the location search field' => 'Hide the location search field', 'Hide Map' => 'Hide Map', 'Hide the map' => 'Hide the map', 'Hide Address' => 'Hide Address', 'Hide the address fields' => 'Hide the address fields', 'Show Latitude / Longitude' => 'Show Latitude / Longitude', 'Show the latitude / longitude fields' => 'Show the latitude / longitude fields', 'Show Current Location' => 'Show Current Location', 'Show a button to centre the map on the users current location' => 'Show a button to centre the map on the users current location', 'Field Size' => 'Field Size', 'Choose the size of the field to display' => 'Choose the size of the field to display', 'Normal' => 'Normal', 'Mini' => 'Mini', 'All Countries' => 'All Countries', 'Preferred Country' => 'Preferred Country', 'When searching for a location, results in this country will take precedence. Be aware that some services will show results ONLY within this country.' => 'When searching for a location, results in this country will take precedence. Be aware that some services will show results ONLY within this country.', 'Enable what3words grid' => 'Enable what3words grid', 'Will enable the what3words grid overlay when zoomed in above 17' => 'Will enable the what3words grid overlay when zoomed in above 17', 'Show what3words field' => 'Show what3words field', 'Show the what3words field for the selected location' => 'Show the what3words field for the selected location', // Settings // ========================================================================= 'Select your map style' => 'Select your map style', 'Map Tiles' => 'Map Tiles', 'Select the style of map tiles.' => 'Select the style of map tiles.', 'Map Token' => 'Map Token', 'Add the API key for map tiles service you are using.' => 'Add the API key for map tiles service you are using.', 'Geo Service' => 'Geo Service', 'Select the service to be used for Geocoding.' => 'Select the service to be used for Geocoding.', 'Geo Token' => 'Geo Token', 'Add the API key for the geocoding service.' => 'Add the API key for the geocoding service.', 'Private Key' => 'Private Key', 'Paste the contents of your private key files below (supports env variables).' => 'Paste the contents of your private key files below (supports env variables).', 'Key ID' => 'Key ID', 'The ID of the key associated with your private key.' => 'The ID of the key associated with your private key.', 'Team ID' => 'Team ID', 'The team ID that created the key ID and private key.' => 'The team ID that created the key ID and private key.', 'Notice' => 'Notice', 'MapKit does not support individual address parts.' => 'MapKit does not support individual address parts.', 'App ID' => 'App ID', 'Your Here app ID.' => 'Your Here app ID.', 'App Code' => 'App Code', 'Your Here app code.' => 'Your Here app code.', 'API Key' => 'API Key', 'Your Here API Key (only required for front-end embed maps).' => 'Your Here API Key (only required for front-end embed maps).', 'Geolocation Service' => 'Geolocation Service', 'Select the service to be used for Geolocating users.' => 'Select the service to be used for Geolocating users.', 'Geolocation Token' => 'Geolocation Token', 'Add the API key for the geolocation service.' => 'Add the API key for the geolocation service.', 'Account ID' => 'Account ID', 'Your MaxMind account ID.' => 'Your MaxMind account ID.', 'License Key' => 'License Key', 'Your MaxMind license key.' => 'Your MaxMind license key.', 'what3words' => 'what3words', 'Enable what3words Integration' => 'Enable what3words Integration', 'what3words Token' => 'what3words Token', 'Your what3words API key.' => 'Your what3words API key.', 'Getting a what3words API Key' => 'Getting a what3words API Key', 'what3words offer a free public API key.' => 'what3words offer a free public API key.', // Settings: Map Tiles Options // ------------------------------------------------------------------------- 'Open Source' => 'Open Source', 'Wikimedia' => 'Wikimedia', 'OpenStreetMap' => 'OpenStreetMap', 'Carto: Voyager' => 'Carto: Voyager', 'Carto: Positron' => 'Carto: Positron', 'Carto: Dark Matter' => 'Carto: Dark Matter', 'Requires API Key (Token)' => 'Requires API Key (Token)', 'Mapbox: Outdoors' => 'Mapbox: Outdoors', 'Mapbox: Streets' => 'Mapbox: Streets', 'Mapbox: Light' => 'Mapbox: Light', 'Mapbox: Dark' => 'Mapbox: Dark', 'Google Maps: Roadmap' => 'Google Maps: Roadmap', 'Google Maps: Terrain' => 'Google Maps: Terrain', 'Google Maps: Hybrid' => 'Google Maps: Hybrid', 'Apple MapKit: Standard' => 'Apple MapKit: Standard', 'Apple MapKit: Muted Standard' => 'Apple MapKit: Muted Standard', 'Apple MapKit: Satellite' => 'Apple MapKit: Satellite', 'Apple MapKit: Hybrid' => 'Apple MapKit: Hybrid', 'Here: Normal Day' => 'Here: Normal Day', 'Here: Normal Day Grey' => 'Here: Normal Day Grey', 'Here: Normal Day Transit' => 'Here: Normal Day Transit', 'Here: Reduced' => 'Here: Reduced', 'Here: Pedestrian' => 'Here: Pedestrian', 'Here: Terrain' => 'Here: Terrain', 'Here: Satellite' => 'Here: Satellite', 'Here: Hybrid' => 'Here: Hybrid', // Settings: Geo Service Options // ------------------------------------------------------------------------- 'Nominatim' => 'Nominatim', 'Mapbox' => 'Mapbox', 'Google Maps' => 'Google Maps', 'Apple MapKit' => 'Apple MapKit', 'Here' => 'Here', // Settings: Geo Location Services // ------------------------------------------------------------------------- 'None' => 'None', 'ipstack' => 'ipstack', 'MaxMind (Lite, ~60MB download)' => 'MaxMind (Lite, ~60MB download)', 'MaxMind' => 'MaxMind', // Settings: Info // ------------------------------------------------------------------------- 'Getting API Keys' => 'Getting API Keys', 'You will need to enable the **Maps JavaScript API** and **Places API** for if using Google for the map tiles, and the **Places API** and **Geocoding API** if you are using it for the Geo service.' => 'You will need to enable the **Maps JavaScript API** and **Places API** for if using Google for the map tiles, and the **Places API** and **Geocoding API** if you are using it for the Geo service.', 'You can use the same key for both map tiles and geo service, no configuration needed!' => 'You can use the same key for both map tiles and geo service, no configuration needed!', 'We currently only support Apple MapKit for map tiles only.' => 'We currently only support Apple MapKit for map tiles only.', 'Getting Geolocation API Keys' => 'Getting Geolocation API Keys', 'ipstack offer free and paid-for versions of their API.' => 'ipstack offer free and paid-for versions of their API.', 'MaxMind offer free lookup database that must be stored locally, and a paid-for version of their API.' => 'MaxMind offer free lookup database that must be stored locally, and a paid-for version of their API.', ]; ================================================ FILE: src/utilities/StaticMap.php ================================================ <?php /** * Maps for Craft CMS * * @link https://ethercreative.co.uk * @copyright Copyright (c) 2019 Ether Creative */ namespace ether\simplemap\utilities; use Craft; use craft\helpers\Json; use craft\web\Response; use ether\simplemap\enums\MapTiles; use ether\simplemap\models\Marker; use ether\simplemap\models\Point; use ether\simplemap\models\Settings; use ether\simplemap\SimpleMap; use Exception; use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; use Imagine\Image\Box; use Imagine\Image\FontInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\RGB; use Imagine\Image\Point\Center; use Imagine\Imagick\Imagick; use Psr\Http\Message\StreamInterface; /** * Class StaticMap * * Based off https://github.com/dfacts/staticmaplite/blob/master/staticmap.php * * TODO: Support external volumes (i.e. S3, Spaces) * * @author Ether Creative * @package ether\simplemap\utilities */ class StaticMap { // Properties // ========================================================================= const TILE_CACHE_DIR = '@runtime/maps/tiles'; const MAP_CACHE_DIR = '@runtime/maps/maps'; private float $lat; private float $lng; private int $width; private int $height; private int $zoom; private int $scale; private mixed $tiles; private mixed $tileSize; private string $mapTiles; private $centerX, $centerY, $offsetX, $offsetY; private array $markers; /** * @var ImageInterface */ private ImageInterface $image; // Constructor // ========================================================================= /** * StaticMap constructor. * * @param float $lat * @param float $lng * @param int $width * @param int $height * @param int $zoom * @param int $scale * @param string|null $markers * * @throws Exception */ public function __construct ( float $lat = 51.272154, float $lng = 0.514951, int $width = 640, int $height = 480, int $zoom = 15, int $scale = 1, string $markers = null ) { $this->lat = $lat; $this->lng = $lng; $this->width = $width; $this->height = $height; $this->zoom = $zoom; $this->scale = $scale; if (empty($markers)) $this->markers = []; else { $this->markers = array_map(function ($m) { $m = explode('|', $m); return new Marker([ 'location' => Json::decode($m[0]), 'color' => $m[1], 'label' => $m[2], ]); }, explode(';', urldecode($markers))); } /** @var Settings $settings */ $settings = SimpleMap::getInstance()->getSettings(); $tiles = MapTiles::getTiles($settings->mapTiles, $scale); $this->tiles = $tiles['url']; $this->tileSize = $tiles['size']; $this->mapTiles = $settings->mapTiles; } // Public Methods // ========================================================================= public function render () { $filename = $this->_mapCacheIdToFilename(); if ($this->_checkMapCache()) return $this->_send(file_get_contents($filename)); $this->_initCoords(); $this->_createBaseMap(); $this->_placeMarkers(); self::_mkdirRecursive(dirname($filename), 0777); $this->image->save($filename); if (file_exists($filename)) return $this->_send(file_get_contents($filename)); return $this->_send($this->image->show('png')); } // Private Methods // ========================================================================= private function _initCoords (): void { $this->centerX = $this->_lngToTile($this->lng); $this->centerY = $this->_latToTile($this->lat); } private function _createBaseMap (): void { $imagine = $this->_getImagine(); $palette = new RGB(); $w = $this->width * $this->scale; $h = $this->height * $this->scale; $_ts = $this->tileSize * $this->scale; $this->image = $imagine->create(new Box($w, $h)); $startX = floor($this->centerX - ($w / $_ts) / 2); $startY = floor($this->centerY - ($h / $_ts) / 2); $endX = ceil($this->centerX + ($w / $_ts) / 2); $endY = ceil($this->centerY + ($h / $_ts) / 2); $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $_ts); $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $_ts); $this->offsetX += floor($w / 2); $this->offsetY += floor($h / 2); $this->offsetX += floor($startX - floor($this->centerX)) * $_ts; $this->offsetY += floor($startY - floor($this->centerY)) * $_ts; for ($x = $startX; $x <= $endX; $x++) { for ($y = $startY; $y <= $endY; $y++) { $url = str_replace( ['{z}', '{x}', '{y}'], [$this->zoom, $x, $y], $this->tiles ); $tileData = $this->_fetchTile($url); if ($tileData) { $tileImg = $imagine->load($tileData); } else { $tileImg = $imagine->create(new Box($_ts, $_ts)); $tileImg->draw()->text( 'err', null, new Point($_ts / 2, $_ts / 2), $palette->color('#fff', 100) ); } $destX = ($x - $startX) * $_ts + $this->offsetX; $destY = ($y - $startY) * $_ts + $this->offsetY; $this->image->paste( $tileImg, new Point($destX, $destY) ); } } } private function _placeMarkers (): void { $w = $this->width * $this->scale; $h = $this->height * $this->scale; $_ts = $this->tileSize * $this->scale; /** @var Marker $marker */ foreach ($this->markers as $marker) { $img = $this->_renderMarker( $marker->color, $marker->label ); $pos = explode(',', $marker->getLocation(true)); $x = floor(($w / 2) - $_ts * ($this->centerX - $this->_lngToTile($pos[1]))); $y = floor(($h / 2) - $_ts * ($this->centerY - $this->_latToTile($pos[0]))); $x -= $img->getSize()->getWidth() / 2; $y -= $img->getSize()->getHeight(); $this->image->paste( $img, new Point($x, $y) ); } } private function _send ($file) { $response = Craft::$app->getResponse(); $response->format = Response::FORMAT_RAW; $expires = 60 * 60 * 24 * 14; $headers = $response->getHeaders(); $headers->set('content-type', 'image/png'); $headers->set('cache-control', 'maxage=' . $expires); $headers->set('expires', gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT'); return $file; } // Helpers // ========================================================================= public static function getLabelColour ($color): string { $r = hexdec($color[1] . $color[2]); $g = hexdec($color[3] . $color[4]); $b = hexdec($color[5] . $color[6]); return (($r * 299 + $g * 587 + $b * 114) / 1000 > 130) ? '000' : 'fff'; } private static function _join (): string { $paths = func_get_args(); $paths = array_map(function ($p) { return rtrim($p, '/'); }, $paths); $paths = array_filter($paths); return join('/', $paths); } private static function _mkdirRecursive ($pathname, $mode): bool { is_dir(dirname($pathname)) || self::_mkdirRecursive(dirname($pathname), $mode); return is_dir($pathname) || mkdir($pathname, $mode); } // Imagine // ------------------------------------------------------------------------- private function _getImageDriver () { static $driver; if ($driver) return $driver; $generalConfig = Craft::$app->getConfig()->getGeneral(); $extension = strtolower($generalConfig->imageDriver); if ($extension === 'gd' || Craft::$app->getImages()->getIsGd()) $driver = 'gd'; else $driver = 'imagick'; return $driver; } private function _getImagine () { static $imagine; if ($imagine) return $imagine; if ($this->_getImageDriver() === 'gd') { $imagine = new \Imagine\Gd\Imagine(); } else { $imagine = new \Imagine\Imagick\Imagine(); } return $imagine; } private function _getFont (ColorInterface $colour, $size = 10): \Imagine\Imagick\Font|FontInterface|\Imagine\Gd\Font { $key = ((string) $colour) . '-' . $size; /** @var FontInterface[] $fonts */ static $fonts = []; if (array_key_exists($key, $fonts)) return $fonts[$key]; $file = Craft::getAlias('@simplemap/resources/OpenSans-Bold.ttf'); if ($this->_getImageDriver() === 'gd') $fonts[$key] = new \Imagine\Gd\Font($file, $size, $colour); else $fonts[$key] = new \Imagine\Imagick\Font(new Imagick(), $file, $size, $colour); return $fonts[$key]; } private function _renderMarker ($colour, $label = null): ImageInterface { $resizeMultiplier = 0.1 * $this->scale; $fontSize = 12 * $this->scale; $fontOffset = 4 * $this->scale; $svg = $label === null ? 'markerNoLabel.png' : 'marker.png'; $img = $this->_getImagine()->open( Craft::getAlias('@simplemap/resources/' . $svg) ); $img->resize(new Box( $img->getSize()->getWidth() * $resizeMultiplier, $img->getSize()->getHeight() * $resizeMultiplier ), ImageInterface::FILTER_MITCHELL); $img->effects()->colorize($img->palette()->color($colour)); if ($label !== null) { $textColour = $img->palette()->color(self::getLabelColour($colour)); $imgCenter = new Center($img->getSize()); $font = $this->_getFont($textColour, $fontSize); $textCenter = new Center($font->box($label)); $img->draw()->text( $label, $font, new Point( $imgCenter->getX() - $textCenter->getX(), $fontOffset ) ); } return $img; } // Tiles // ------------------------------------------------------------------------- private function _latToTile ($lat): float|int { return (1 - log(tan($lat * pi() / 180) + 1 / cos($lat * pi() / 180)) / pi()) / 2 * pow(2, $this->zoom); } private function _lngToTile ($lng): float|int { return (($lng + 180) / 360) * pow(2, $this->zoom); } /** * @throws GuzzleException */ private function _fetchTile ($url): StreamInterface|string { if ($cached = $this->_checkTileCache($url)) return $cached; $client = new Client(); $res = $client->get($url); $tile = $res->getBody(); $this->_writeTileToCache($url, $tile); return $tile; } // Map // ------------------------------------------------------------------------- private function _getMapId (): string { return md5( http_build_query([ 'lat' => $this->lat, 'lng' => $this->lng, 'width' => $this->width, 'height' => $this->height, 'zoom' => $this->zoom, 'scale' => $this->scale, 'tiles' => $this->mapTiles, 'markers' => $this->markers, ]) ); } // Cache // ------------------------------------------------------------------------- private static function _tileCache (): bool|string { return Craft::getAlias(self::TILE_CACHE_DIR); } private static function _mapCache (): bool|string { return Craft::getAlias(self::MAP_CACHE_DIR); } private function _tileUrlToFilename ($url): string { return self::_join( self::_tileCache(), str_replace(['http://', 'https://'], '', $url) ); } private function _mapCacheIdToFilename (): string { $id = $this->_getMapId(); return self::_join( self::_mapCache(), substr($id, 0, 2), substr($id, 2, 2), substr($id, 4) ) . '.png'; } private function _checkTileCache ($url): bool|string|null { $filename = $this->_tileUrlToFilename($url); return file_exists($filename) ? file_get_contents($filename) : null; } private function _checkMapCache (): bool { return file_exists($this->_mapCacheIdToFilename()); } private function _writeTileToCache ($url, $data): void { $filename = $this->_tileUrlToFilename($url); self::_mkdirRecursive(dirname($filename), 0777); file_put_contents($filename, $data); } } ================================================ FILE: src/web/Variable.php ================================================ <?php /** * Maps for Craft CMS 3 * * @link https://ethercreative.co.uk * @copyright Copyright (c) 2019 Ether Creative */ namespace ether\simplemap\web; use Craft; use craft\errors\DeprecationException; use ether\simplemap\models\Settings; use ether\simplemap\models\UserLocation; use ether\simplemap\services\GeoService; use ether\simplemap\SimpleMap; use Exception; use yii\base\InvalidConfigException; /** * Class Variable * * @author Ether Creative * @package ether\simplemap\web */ class Variable { /** * Returns the map token * * @return string */ public function getMapToken (): string { /** @var Settings $settings */ $settings = SimpleMap::getInstance()->getSettings(); return GeoService::getToken( $settings->getMapToken(), $settings->mapTiles ); } /** * Returns the map token * * @deprecated as of 3.4.0 * @return string * @throws DeprecationException */ public function getApiKey (): string { Craft::$app->getDeprecator()->log( 'Variable::getApiKey()', 'ether\simplemap\web\Variable::getApiKey() has been deprecated. Use `getMapToken()` instead.' ); return $this->getMapToken(); } /** * Returns the current users approximate location * * @param string|null $ip - Override the lookup IP * * @return UserLocation|null * @throws Exception */ public function getUserLocation (string $ip = null): ?UserLocation { return SimpleMap::getInstance()->geolocation->lookup($ip); } /** * Converts the given address to lat/lng * * @param string $address The address to search * @param string|null $country The ISO 3166-1 alpha-2 country code to * restrict the search to * * @return array|null */ public function getLatLngFromAddress (string $address, string $country = null): ?array { try { return GeoService::latLngFromAddress($address, $country); } catch (Exception $e) { Craft::error($e->getMessage(), 'simplemap'); return [ 'lat' => '', 'lng' => '', ]; } } /** * Will return a static map image using the given options. * * @param $options - See StaticOptions for the available options * * @return string * @throws Exception */ public function getImg ($options): string { return SimpleMap::getInstance()->static->generate($options); } /** * Will return a static map image ready for srcset * * @param $options - See StaticOptions for the available options * * @return string * @throws Exception */ public function getImgSrcSet ($options): string { $x1 = $this->getImg(array_merge($options, ['scale' => 1])); $x2 = $this->getImg(array_merge($options, ['scale' => 2])); return $x1 . ' 1x, ' . $x2 . ' 2x'; } /** * Will return markup for a dynamic map embed * * @param $options - See EmbedOptions for the available options * * @return string|void * @throws InvalidConfigException */ public function getEmbed ($options) { return SimpleMap::getInstance()->embed->embed($options); } } ================================================ FILE: src/web/assets/MapAsset.php ================================================ <?php /** * SimpleMap for Craft CMS * * @link https://ethercreative.co.uk * @copyright Copyright (c) 2019 Ether Creative */ namespace ether\simplemap\web\assets; use craft\web\AssetBundle; use craft\web\assets\cp\CpAsset; use craft\web\assets\vue\VueAsset; /** * Class MapAsset * * @author Ether Creative * @package ether\simplemap\web\assets */ class MapAsset extends AssetBundle { public function init () { $this->sourcePath = __DIR__ . '/map'; $this->depends = [ CpAsset::class, VueAsset::class, ]; if (getenv('ETHER_ENVIRONMENT') === 'true') { $this->js = [ 'https://localhost:8080/app.js', ]; } else { $this->css = [ 'css/app.css', 'css/chunk-vendors.css', ]; $this->js = [ 'js/app.js', 'js/chunk-vendors.js', ]; } parent::init(); } } ================================================ FILE: src/web/assets/map/css/app.css ================================================ .Search_wrap_rNtoB{position:relative;display:block}@media only screen and (max-width:767px){.Search_wrap_rNtoB{margin-right:44px}}.Search_wrap_rNtoB,.Search_wrap_rNtoB *{-webkit-box-sizing:border-box;box-sizing:border-box}.Search_wrap_rNtoB>svg{position:absolute;top:17px;left:16px;pointer-events:none}.Search_wrap_rNtoB.Search_no-map_1K6JR{margin-right:0}.Search_input_3tpBT{width:100%;padding:16px 0 15px 48px;font-size:16px;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border:none;border-bottom:1px solid transparent;border-radius:5px;-webkit-box-shadow:0 2px 15px 0 rgba(0,0,0,.2);box-shadow:0 2px 15px 0 rgba(0,0,0,.2)}.Search_input_3tpBT.Search_no-map_1K6JR{-webkit-box-shadow:none;box-shadow:none;border:1px solid #dce4ea}.Search_open_a4lCm .Search_input_3tpBT{border-radius:5px 5px 0 0;border-bottom-color:#d2dbe1}.Search_resultsWrap_1ufuc{position:absolute;top:100%;left:-20px;width:calc(100% + 40px);height:210px;padding:0 20px 20px;overflow:hidden;pointer-events:none}.Search_results_nREcw{position:relative;z-index:2;width:100%;padding:7px 0;background-color:#fff;-webkit-box-shadow:0 5px 15px 0 rgba(0,0,0,.1);box-shadow:0 5px 15px 0 rgba(0,0,0,.1);border-radius:0 0 5px 5px;-webkit-transform:translateY(calc(-100% - 15px));transform:translateY(calc(-100% - 15px));-webkit-transition:-webkit-transform .3s ease;transition:-webkit-transform .3s ease;transition:transform .3s ease;transition:transform .3s ease,-webkit-transform .3s ease;pointer-events:all}.Search_open_a4lCm .Search_results_nREcw{-webkit-transform:translateY(0);transform:translateY(0)}.Search_results_nREcw li{padding:7px 14px;-webkit-transition:background-color .15s ease;transition:background-color .15s ease}.Search_results_nREcw li[class*=highlighted]{background-color:#e4edf3;cursor:pointer}.Label_label_GHq68{position:relative;display:block;border-right:1px solid #dce4ea}.Label_label_GHq68:not(:last-child){border-bottom:1px solid #dce4ea}.Label_name_3Feow{position:absolute;top:9px;left:12px;display:block;color:rgba(41,50,61,.41);font-size:12px;font-weight:500;letter-spacing:0}.Input_input_1qTm5{width:100%;padding:29px 0 10px;color:#29323d;font-size:15px;letter-spacing:0;text-indent:12px;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:none;border-radius:0;-webkit-transition:color .15s ease;transition:color .15s ease}.Input_input_1qTm5:disabled{opacity:.5}.Address_grid_3Vxrg{display:grid;grid-template-columns:1fr 1fr;background-color:#fff;border-radius:5px;-webkit-box-shadow:0 2px 15px 0 rgba(0,0,0,.2);box-shadow:0 2px 15px 0 rgba(0,0,0,.2);overflow:hidden;-webkit-transition:opacity .3s ease,-webkit-transform .3s ease;transition:opacity .3s ease,-webkit-transform .3s ease;transition:transform .3s ease,opacity .3s ease;transition:transform .3s ease,opacity .3s ease,-webkit-transform .3s ease}.Address_grid_3Vxrg:not(:first-child){margin-top:24px}.Address_grid_3Vxrg.Address_fade_1Khjf{opacity:.8}.Address_grid_3Vxrg.Address_no-map_kB9Ag{-webkit-box-shadow:none;box-shadow:none;border:1px solid #dce4ea}@media only screen and (max-width:1199px){.Address_grid_3Vxrg{grid-template-columns:1fr}.Address_grid_3Vxrg label{border-right:none}}@media only screen and (max-width:767px){.Address_grid_3Vxrg:not(.Address_no-map_kB9Ag):not(.Address_no-search_1JpdT){margin-top:200px!important}.Address_grid_3Vxrg:not(.Address_no-map_kB9Ag).Address_no-search_1JpdT{margin-top:260px!important}}.Address_grid_3Vxrg,.Address_grid_3Vxrg *{-webkit-box-sizing:border-box;box-sizing:border-box}.Address_grid_3Vxrg label.Address_right_3dCTx{border-right:none}.Address_grid_3Vxrg .Address_full_2Hoxx,.Address_grid_3Vxrg label:last-child{grid-column:span 2;border-right:none}@media only screen and (max-width:1199px){.Address_grid_3Vxrg .Address_full_2Hoxx,.Address_grid_3Vxrg label:last-child{grid-column:span 1}}.Address_row_3kuz9{display:-webkit-box;display:-ms-flexbox;display:flex}.Address_row_3kuz9:last-child>*{border-bottom:none}.Address_row_3kuz9 label{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;border-right:none}.Address_btn_2BSW6{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:none;border-bottom:1px solid #dce4ea;border-radius:0 5px 0 0;cursor:pointer;pointer-events:none}.Address_btn_2BSW6 svg{opacity:.5;-webkit-transform:translateX(200%);transform:translateX(200%);-webkit-transition:opacity .15s ease,-webkit-transform .3s ease;transition:opacity .15s ease,-webkit-transform .3s ease;transition:transform .3s ease,opacity .15s ease;transition:transform .3s ease,opacity .15s ease,-webkit-transform .3s ease}.Address_btn_2BSW6 path{-webkit-transition:fill .15s ease;transition:fill .15s ease}.Address_show-clear_rAelt .Address_btn_2BSW6{pointer-events:auto}.Address_show-clear_rAelt .Address_btn_2BSW6 svg{-webkit-transform:translateX(0);transform:translateX(0)}.Address_btn_2BSW6:hover svg{opacity:1}.Address_btn_2BSW6:hover path{fill:#f22c26}.Address_delete_gHZG0 input{color:#f22c26}.Map_map_2NpD9{position:absolute;z-index:0;top:0;left:0;right:0;bottom:0;-webkit-box-sizing:border-box;box-sizing:border-box}@media only screen and (max-width:767px){.Map_map_2NpD9{height:300px;bottom:auto}}.Map_control_3-EIS{margin:24px!important;font-size:0;border-radius:5px;-webkit-box-shadow:0 2px 15px 0 rgba(0,0,0,.2);box-shadow:0 2px 15px 0 rgba(0,0,0,.2)}@media only screen and (max-width:767px){.Map_control_3-EIS{margin:14px!important}}.Map_control_3-EIS button{display:block;width:100%;padding:2px 10px 3px;color:rgba(41,50,61,.75);font-size:20px;font-weight:700;line-height:normal;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border:none;cursor:pointer;-webkit-transition:background-color .15s ease;transition:background-color .15s ease}.Map_control_3-EIS button:not(:last-child){border-bottom:1px solid #dce4ea}.Map_control_3-EIS button:first-child{padding-bottom:4px;border-radius:5px 5px 0 0}.Map_control_3-EIS button:last-child{padding-top:4px;border-radius:0 0 5px 5px}.Map_control_3-EIS button:hover{background-color:#f0f9ff}.App_wrap_2esTH{position:relative;margin:0 -24px;min-height:284px;overflow:hidden}@media only screen and (max-width:767px){.App_wrap_2esTH{margin:0 -12px}}.hud .App_wrap_2esTH{margin:-24px!important}.matrixblock .App_wrap_2esTH{margin:0 -14px!important}.superTable-layout-row .App_wrap_2esTH,.superTable-layout-table .App_wrap_2esTH{margin:-4px -10px!important}.App_wrap_2esTH.App_no-map_2mvF1{min-height:0;margin:0;overflow:visible}.App_wrap_2esTH.App_no-map_2mvF1 .App_content_pIJBB{padding:0}.hud .App_wrap_2esTH.App_no-map_2mvF1 .App_content_pIJBB{width:100%;padding:24px}.App_mini_RpxNW{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.App_mini_RpxNW .btn{margin-left:24px;font-size:14px}.App_empty_1A4ND{opacity:.5}.App_content_pIJBB{position:relative;z-index:2;-webkit-box-sizing:border-box;box-sizing:border-box;width:50%;padding:24px;pointer-events:none}.App_content_pIJBB.App_noMap_Mu8tf{width:100%}.matrixblock .App_content_pIJBB,.superTable-layout-row .App_content_pIJBB,.superTable-layout-table .App_content_pIJBB{padding:14px}@media only screen and (max-width:767px){.App_content_pIJBB{width:100%;padding:12px}}.App_content_pIJBB>*{pointer-events:all} ================================================ FILE: src/web/assets/map/css/chunk-vendors.css ================================================ .leaflet-image-layer,.leaflet-layer,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-pane,.leaflet-pane>canvas,.leaflet-pane>svg,.leaflet-tile,.leaflet-tile-container,.leaflet-zoom-box{position:absolute;left:0;top:0}.leaflet-container{overflow:hidden}.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-tile{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-drag:none}.leaflet-tile::-moz-selection{background:transparent}.leaflet-tile::selection{background:transparent}.leaflet-safari .leaflet-tile{image-rendering:-webkit-optimize-contrast}.leaflet-safari .leaflet-tile-container{width:1600px;height:1600px;-webkit-transform-origin:0 0}.leaflet-marker-icon,.leaflet-marker-shadow{display:block}.leaflet-container .leaflet-marker-pane img,.leaflet-container .leaflet-overlay-pane svg,.leaflet-container .leaflet-shadow-pane img,.leaflet-container .leaflet-tile,.leaflet-container .leaflet-tile-pane img,.leaflet-container img.leaflet-image-layer{max-width:none!important;max-height:none!important}.leaflet-container.leaflet-touch-zoom{-ms-touch-action:pan-x pan-y;touch-action:pan-x pan-y}.leaflet-container.leaflet-touch-drag{-ms-touch-action:pinch-zoom;touch-action:none;touch-action:pinch-zoom}.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom{-ms-touch-action:none;touch-action:none}.leaflet-container{-webkit-tap-highlight-color:transparent}.leaflet-container a{-webkit-tap-highlight-color:rgba(51,181,229,.4)}.leaflet-tile{-webkit-filter:inherit;filter:inherit;visibility:hidden}.leaflet-tile-loaded{visibility:inherit}.leaflet-zoom-box{width:0;height:0;-webkit-box-sizing:border-box;box-sizing:border-box;z-index:800}.leaflet-overlay-pane svg{-moz-user-select:none}.leaflet-pane{z-index:400}.leaflet-tile-pane{z-index:200}.leaflet-overlay-pane{z-index:400}.leaflet-shadow-pane{z-index:500}.leaflet-marker-pane{z-index:600}.leaflet-tooltip-pane{z-index:650}.leaflet-popup-pane{z-index:700}.leaflet-map-pane canvas{z-index:100}.leaflet-map-pane svg{z-index:200}.leaflet-vml-shape{width:1px;height:1px}.lvml{behavior:url(#default#VML);display:inline-block;position:absolute}.leaflet-control{position:relative;z-index:800;pointer-events:visiblePainted;pointer-events:auto}.leaflet-bottom,.leaflet-top{position:absolute;z-index:1000;pointer-events:none}.leaflet-top{top:0}.leaflet-right{right:0}.leaflet-bottom{bottom:0}.leaflet-left{left:0}.leaflet-control{float:left;clear:both}.leaflet-right .leaflet-control{float:right}.leaflet-top .leaflet-control{margin-top:10px}.leaflet-bottom .leaflet-control{margin-bottom:10px}.leaflet-left .leaflet-control{margin-left:10px}.leaflet-right .leaflet-control{margin-right:10px}.leaflet-fade-anim .leaflet-tile{will-change:opacity}.leaflet-fade-anim .leaflet-popup{opacity:0;-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.leaflet-fade-anim .leaflet-map-pane .leaflet-popup{opacity:1}.leaflet-zoom-animated{-webkit-transform-origin:0 0;transform-origin:0 0}.leaflet-zoom-anim .leaflet-zoom-animated{will-change:transform;-webkit-transition:-webkit-transform .25s cubic-bezier(0,0,.25,1);transition:-webkit-transform .25s cubic-bezier(0,0,.25,1);transition:transform .25s cubic-bezier(0,0,.25,1);transition:transform .25s cubic-bezier(0,0,.25,1),-webkit-transform .25s cubic-bezier(0,0,.25,1)}.leaflet-pan-anim .leaflet-tile,.leaflet-zoom-anim .leaflet-tile{-webkit-transition:none;transition:none}.leaflet-zoom-anim .leaflet-zoom-hide{visibility:hidden}.leaflet-interactive{cursor:pointer}.leaflet-grab{cursor:-webkit-grab;cursor:grab}.leaflet-crosshair,.leaflet-crosshair .leaflet-interactive{cursor:crosshair}.leaflet-control,.leaflet-popup-pane{cursor:auto}.leaflet-dragging .leaflet-grab,.leaflet-dragging .leaflet-grab .leaflet-interactive,.leaflet-dragging .leaflet-marker-draggable{cursor:move;cursor:-webkit-grabbing;cursor:grabbing}.leaflet-image-layer,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-pane>svg path,.leaflet-tile-container{pointer-events:none}.leaflet-image-layer.leaflet-interactive,.leaflet-marker-icon.leaflet-interactive,.leaflet-pane>svg path.leaflet-interactive,svg.leaflet-image-layer.leaflet-interactive path{pointer-events:visiblePainted;pointer-events:auto}.leaflet-container{background:#ddd;outline:0}.leaflet-container a{color:#0078a8}.leaflet-container a.leaflet-active{outline:2px solid orange}.leaflet-zoom-box{border:2px dotted #38f;background:hsla(0,0%,100%,.5)}.leaflet-container{font:12px/1.5 Helvetica Neue,Arial,Helvetica,sans-serif}.leaflet-bar{-webkit-box-shadow:0 1px 5px rgba(0,0,0,.65);box-shadow:0 1px 5px rgba(0,0,0,.65);border-radius:4px}.leaflet-bar a,.leaflet-bar a:hover{background-color:#fff;border-bottom:1px solid #ccc;width:26px;height:26px;line-height:26px;display:block;text-align:center;text-decoration:none;color:#000}.leaflet-bar a,.leaflet-control-layers-toggle{background-position:50% 50%;background-repeat:no-repeat;display:block}.leaflet-bar a:hover{background-color:#f4f4f4}.leaflet-bar a:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-bar a:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-bottom:none}.leaflet-bar a.leaflet-disabled{cursor:default;background-color:#f4f4f4;color:#bbb}.leaflet-touch .leaflet-bar a{width:30px;height:30px;line-height:30px}.leaflet-touch .leaflet-bar a:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.leaflet-touch .leaflet-bar a:last-child{border-bottom-left-radius:2px;border-bottom-right-radius:2px}.leaflet-control-zoom-in,.leaflet-control-zoom-out{font:700 18px Lucida Console,Monaco,monospace;text-indent:1px}.leaflet-touch .leaflet-control-zoom-in,.leaflet-touch .leaflet-control-zoom-out{font-size:22px}.leaflet-control-layers{-webkit-box-shadow:0 1px 5px rgba(0,0,0,.4);box-shadow:0 1px 5px rgba(0,0,0,.4);background:#fff;border-radius:5px}.leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAQAAAADQ4RFAAACf0lEQVR4AY1UM3gkARTePdvdoTxXKc+qTl3aU5U6b2Kbkz3Gtq3Zw6ziLGNPzrYx7946Tr6/ee/XeCQ4D3ykPtL5tHno4n0d/h3+xfuWHGLX81cn7r0iTNzjr7LrlxCqPtkbTQEHeqOrTy4Yyt3VCi/IOB0v7rVC7q45Q3Gr5K6jt+3Gl5nCoDD4MtO+j96Wu8atmhGqcNGHObuf8OM/x3AMx38+4Z2sPqzCxRFK2aF2e5Jol56XTLyggAMTL56XOMoS1W4pOyjUcGGQdZxU6qRh7B9Zp+PfpOFlqt0zyDZckPi1ttmIp03jX8gyJ8a/PG2yutpS/Vol7peZIbZcKBAEEheEIAgFbDkz5H6Zrkm2hVWGiXKiF4Ycw0RWKdtC16Q7qe3X4iOMxruonzegJzWaXFrU9utOSsLUmrc0YjeWYjCW4PDMADElpJSSQ0vQvA1Tm6/JlKnqFs1EGyZiFCqnRZTEJJJiKRYzVYzJck2Rm6P4iH+cmSY0YzimYa8l0EtTODFWhcMIMVqdsI2uiTvKmTisIDHJ3od5GILVhBCarCfVRmo4uTjkhrhzkiBV7SsaqS+TzrzM1qpGGUFt28pIySQHR6h7F6KSwGWm97ay+Z+ZqMcEjEWebE7wxCSQwpkhJqoZA5ivCdZDjJepuJ9IQjGGUmuXJdBFUygxVqVsxFsLMbDe8ZbDYVCGKxs+W080max1hFCarCfV+C1KATwcnvE9gRRuMP2prdbWGowm1KB1y+zwMMENkM755cJ2yPDtqhTI6ED1M/82yIDtC/4j4BijjeObflpO9I9MwXTCsSX8jWAFeHr05WoLTJ5G8IQVS/7vwR6ohirYM7f6HzYpogfS3R2OAAAAAElFTkSuQmCC);width:36px;height:36px}.leaflet-retina .leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAQAAABvcdNgAAAEsklEQVR4AWL4TydIhpZK1kpWOlg0w3ZXP6D2soBtG42jeI6ZmQTHzAxiTbSJsYLjO9HhP+WOmcuhciVnmHVQcJnp7DFvScowZorad/+V/fVzMdMT2g9Cv9guXGv/7pYOrXh2U+RRR3dSd9JRx6bIFc/ekqHI29JC6pJ5ZEh1yWkhkbcFeSjxgx3L2m1cb1C7bceyxA+CNjT/Ifff+/kDk2u/w/33/IeCMOSaWZ4glosqT3DNnNZQ7Cs58/3Ce5HL78iZH/vKVIaYlqzfdLu8Vi7dnvUbEza5Idt36tquZFldl6N5Z/POLof0XLK61mZCmJSWjVF9tEjUluu74IUXvgttuVIHE7YxSkaYhJZam7yiM9Pv82JYfl9nptxZaxMJE4YSPty+vF0+Y2up9d3wwijfjZbabqm/3bZ9ecKHsiGmRflnn1MW4pjHf9oLufyn2z3y1D6n8g8TZhxyzipLNPnAUpsOiuWimg52psrTZYnOWYNDTMuWBWa0tJb4rgq1UvmutpaYEbZlwU3CLJm/ayYjHW5/h7xWLn9Hh1vepDkyf7dE7MtT5LR4e7yYpHrkhOUpEfssBLq2pPhAqoSWKUkk7EDqkmK6RrCEzqDjhNDWNE+XSMvkJRDWlZTmCW0l0PHQGRZY5t1L83kT0Y3l2SItk5JAWHl2dCOBm+fPu3fo5/3v61RMCO9Jx2EEYYhb0rmNQMX/vm7gqOEJLcXTGw3CAuRNeyaPWwjR8PRqKQ1PDA/dpv+on9Shox52WFnx0KY8onHayrJzm87i5h9xGw/tfkev0jGsQizqezUKjk12hBMKJ4kbCqGPVNXudyyrShovGw5CgxsRICxF6aRmSjlBnHRzg7Gx8fKqEubI2rahQYdR1YgDIRQO7JvQyD52hoIQx0mxa0ODtW2Iozn1le2iIRdzwWewedyZzewidueOGqlsn1MvcnQpuVwLGG3/IR1hIKxCjelIDZ8ldqWz25jWAsnldEnK0Zxro19TGVb2ffIZEsIO89EIEDvKMPrzmBOQcKQ+rroye6NgRRxqR4U8EAkz0CL6uSGOm6KQCdWjvjRiSP1BPalCRS5iQYiEIvxuBMJEWgzSoHADcVMuN7IuqqTeyUPq22qFimFtxDyBBJEwNyt6TM88blFHao/6tWWhuuOM4SAK4EI4QmFHA+SEyWlp4EQoJ13cYGzMu7yszEIBOm2rVmHUNqwAIQabISNMRstmdhNWcFLsSm+0tjJH1MdRxO5Nx0WDMhCtgD6OKgZeljJqJKc9po8juskR9XN0Y1lZ3mWjLR9JCO1jRDMd0fpYC2VnvjBSEFg7wBENc0R9HFlb0xvF1+TBEpF68d+DHR6IOWVv2BECtxo46hOFUBd/APU57WIoEwJhIi2CdpyZX0m93BZicktMj1AS9dClteUFAUNUIEygRZCtik5zSxI9MubTBH1GOiHsiLJ3OCoSZkILa9PxiN0EbvhsAo8tdAf9Seepd36lGWHmtNANTv5Jd0z4QYyeo/UEJqxKRpg5LZx6btLPsOaEmdMyxYdlc8LMaJnikDlhclqmPiQnTEpLUIZEwkRagjYkEibQErwhkTAKCLQEbUgkzJQWc/0PstHHcfEdQ+UAAAAASUVORK5CYII=);background-size:26px 26px}.leaflet-touch .leaflet-control-layers-toggle{width:44px;height:44px}.leaflet-control-layers-expanded .leaflet-control-layers-toggle,.leaflet-control-layers .leaflet-control-layers-list{display:none}.leaflet-control-layers-expanded .leaflet-control-layers-list{display:block;position:relative}.leaflet-control-layers-expanded{padding:6px 10px 6px 6px;color:#333;background:#fff}.leaflet-control-layers-scrollbar{overflow-y:scroll;overflow-x:hidden;padding-right:5px}.leaflet-control-layers-selector{margin-top:2px;position:relative;top:1px}.leaflet-control-layers label{display:block}.leaflet-control-layers-separator{height:0;border-top:1px solid #ddd;margin:5px -10px 5px -6px}.leaflet-default-icon-path{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbDNpjszW24mRt28p47v7zq/bXZtrp/lWnXr337j3nPCe85NcypgSFdugCpW5YoDAMRaIMqRi6aKq5E3YqDQO3qAwjVWrD8Ncq/RBpykd8oZUb/kaJutow8r1aP9II0WmLKLIsJyv1w/kqw9Ch2MYdB++12Onxee/QMwvf4/Dk/Lfp/i4nxTXtOoQ4pW5Aj7wpici1A9erdAN2OH64x8OSP9j3Ft3b7aWkTg/Fm91siTra0f9on5sQr9INejH6CUUUpavjFNq1B+Oadhxmnfa8RfEmN8VNAsQhPqF55xHkMzz3jSmChWU6f7/XZKNH+9+hBLOHYozuKQPxyMPUKkrX/K0uWnfFaJGS1QPRtZsOPtr3NsW0uyh6NNCOkU3Yz+bXbT3I8G3xE5EXLXtCXbbqwCO9zPQYPRTZ5vIDXD7U+w7rFDEoUUf7ibHIR4y6bLVPXrz8JVZEql13trxwue/uDivd3fkWRbS6/IA2bID4uk0UpF1N8qLlbBlXs4Ee7HLTfV1j54APvODnSfOWBqtKVvjgLKzF5YdEk5ewRkGlK0i33Eofffc7HT56jD7/6U+qH3Cx7SBLNntH5YIPvODnyfIXZYRVDPqgHtLs5ABHD3YzLuespb7t79FY34DjMwrVrcTuwlT55YMPvOBnRrJ4VXTdNnYug5ucHLBjEpt30701A3Ts+HEa73u6dT3FNWwflY86eMHPk+Yu+i6pzUpRrW7SNDg5JHR4KapmM5Wv2E8Tfcb1HoqqHMHU+uWDD7zg54mz5/2BSnizi9T1Dg4QQXLToGNCkb6tb1NU+QAlGr1++eADrzhn/u8Q2YZhQVlZ5+CAOtqfbhmaUCS1ezNFVm2imDbPmPng5wmz+gwh+oHDce0eUtQ6OGDIyR0uUhUsoO3vfDmmgOezH0mZN59x7MBi++WDL1g/eEiU3avlidO671bkLfwbw5XV2P8Pzo0ydy4t2/0eu33xYSOMOD8hTf4CrBtGMSoXfPLchX+J0ruSePw3LZeK0juPJbYzrhkH0io7B3k164hiGvawhOKMLkrQLyVpZg8rHFW7E2uHOL888IBPlNZ1FPzstSJM694fWr6RwpvcJK60+0HCILTBzZLFNdtAzJaohze60T8qBzyh5ZuOg5e7uwQppofEmf2++DYvmySqGBuKaicF1blQjhuHdvCIMvp8whTTfZzI7RldpwtSzL+F1+wkdZ2TBOW2gIF88PBTzD/gpeREAMEbxnJcaJHNHrpzji0gQCS6hdkEeYt9DF/2qPcEC8RM28Hwmr3sdNyht00byAut2k3gufWNtgtOEOFGUwcXWNDbdNbpgBGxEvKkOQsxivJx33iow0Vw5S6SVTrpVq11ysA2Rp7gTfPfktc6zhtXBBC+adRLshf6sG2RfHPZ5EAc4sVZ83yCN00Fk/4kggu40ZTvIEm5g24qtU4KjBrx/BTTH8ifVASAG7gKrnWxJDcU7x8X6Ecczhm3o6YicvsLXWfh3Ch1W0k8x0nXF+0fFxgt4phz8QvypiwCCFKMqXCnqXExjq10beH+UUA7+nG6mdG/Pu0f3LgFcGrl2s0kNNjpmoJ9o4B29CMO8dMT4Q5ox8uitF6fqsrJOr8qnwNbRzv6hSnG5wP+64C7h9lp30hKNtKdWjtdkbuPA19nJ7Tz3zR/ibgARbhb4AlhavcBebmTHcFl2fvYEnW0ox9xMxKBS8btJ+KiEbq9zA4RthQXDhPa0T9TEe69gWupwc6uBUphquXgf+/FrIjweHQS4/pduMe5ERUMHUd9xv8ZR98CxkS4F2n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=)}.leaflet-container .leaflet-control-attribution{background:#fff;background:hsla(0,0%,100%,.7);margin:0}.leaflet-control-attribution,.leaflet-control-scale-line{padding:0 5px;color:#333}.leaflet-control-attribution a{text-decoration:none}.leaflet-control-attribution a:hover{text-decoration:underline}.leaflet-container .leaflet-control-attribution,.leaflet-container .leaflet-control-scale{font-size:11px}.leaflet-left .leaflet-control-scale{margin-left:5px}.leaflet-bottom .leaflet-control-scale{margin-bottom:5px}.leaflet-control-scale-line{border:2px solid #777;border-top:none;line-height:1.1;padding:2px 5px 1px;font-size:11px;white-space:nowrap;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;background:#fff;background:hsla(0,0%,100%,.5)}.leaflet-control-scale-line:not(:first-child){border-top:2px solid #777;border-bottom:none;margin-top:-2px}.leaflet-control-scale-line:not(:first-child):not(:last-child){border-bottom:2px solid #777}.leaflet-touch .leaflet-bar,.leaflet-touch .leaflet-control-attribution,.leaflet-touch .leaflet-control-layers{-webkit-box-shadow:none;box-shadow:none}.leaflet-touch .leaflet-bar,.leaflet-touch .leaflet-control-layers{border:2px solid rgba(0,0,0,.2);background-clip:padding-box}.leaflet-popup{position:absolute;text-align:center;margin-bottom:20px}.leaflet-popup-content-wrapper{padding:1px;text-align:left;border-radius:12px}.leaflet-popup-content{margin:13px 19px;line-height:1.4}.leaflet-popup-content p{margin:18px 0}.leaflet-popup-tip-container{width:40px;height:20px;position:absolute;left:50%;margin-left:-20px;overflow:hidden;pointer-events:none}.leaflet-popup-tip{width:17px;height:17px;padding:1px;margin:-10px auto 0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.leaflet-popup-content-wrapper,.leaflet-popup-tip{background:#fff;color:#333;-webkit-box-shadow:0 3px 14px rgba(0,0,0,.4);box-shadow:0 3px 14px rgba(0,0,0,.4)}.leaflet-container a.leaflet-popup-close-button{position:absolute;top:0;right:0;padding:4px 4px 0 0;border:none;text-align:center;width:18px;height:14px;font:16px/14px Tahoma,Verdana,sans-serif;color:#c3c3c3;text-decoration:none;font-weight:700;background:transparent}.leaflet-container a.leaflet-popup-close-button:hover{color:#999}.leaflet-popup-scrolled{overflow:auto;border-bottom:1px solid #ddd;border-top:1px solid #ddd}.leaflet-oldie .leaflet-popup-content-wrapper{zoom:1}.leaflet-oldie .leaflet-popup-tip{width:24px;margin:0 auto;-ms-filter:"progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";filter:progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678,M12=0.70710678,M21=-0.70710678,M22=0.70710678)}.leaflet-oldie .leaflet-popup-tip-container{margin-top:-1px}.leaflet-oldie .leaflet-control-layers,.leaflet-oldie .leaflet-control-zoom,.leaflet-oldie .leaflet-popup-content-wrapper,.leaflet-oldie .leaflet-popup-tip{border:1px solid #999}.leaflet-div-icon{background:#fff;border:1px solid #666}.leaflet-tooltip{position:absolute;padding:6px;background-color:#fff;border:1px solid #fff;border-radius:3px;color:#222;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.4);box-shadow:0 1px 3px rgba(0,0,0,.4)}.leaflet-tooltip.leaflet-clickable{cursor:pointer;pointer-events:auto}.leaflet-tooltip-bottom:before,.leaflet-tooltip-left:before,.leaflet-tooltip-right:before,.leaflet-tooltip-top:before{position:absolute;pointer-events:none;border:6px solid transparent;background:transparent;content:""}.leaflet-tooltip-bottom{margin-top:6px}.leaflet-tooltip-top{margin-top:-6px}.leaflet-tooltip-bottom:before,.leaflet-tooltip-top:before{left:50%;margin-left:-6px}.leaflet-tooltip-top:before{bottom:0;margin-bottom:-12px;border-top-color:#fff}.leaflet-tooltip-bottom:before{top:0;margin-top:-12px;margin-left:-6px;border-bottom-color:#fff}.leaflet-tooltip-left{margin-left:-6px}.leaflet-tooltip-right{margin-left:6px}.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{top:50%;margin-top:-6px}.leaflet-tooltip-left:before{right:0;margin-right:-12px;border-left-color:#fff}.leaflet-tooltip-right:before{left:0;margin-left:-12px;border-right-color:#fff} ================================================ FILE: src/web/assets/map/index.html ================================================ <!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><title>Vue App
================================================ FILE: src/web/assets/map/js/app.js ================================================ var EtherMaps=function(e){function t(t){for(var n,s,i=t[0],c=t[1],l=t[2],p=0,h=[];p0&&void 0!==arguments[0]?arguments[0]:{};return Object(i["a"])(this,t),e=Object(f["a"])(this,Object(b["a"])(t).call(this,a,u.GoogleMaps)),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_1",""),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_2",""),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_3",""),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_4",""),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_5",""),Object(o["a"])(Object(g["a"])(e),"airport",""),Object(o["a"])(Object(g["a"])(e),"bus_station",""),Object(o["a"])(Object(g["a"])(e),"colloquial_area",""),Object(o["a"])(Object(g["a"])(e),"establishment",""),Object(o["a"])(Object(g["a"])(e),"floor",""),Object(o["a"])(Object(g["a"])(e),"intersection",""),Object(o["a"])(Object(g["a"])(e),"locality",""),Object(o["a"])(Object(g["a"])(e),"natural_feature",""),Object(o["a"])(Object(g["a"])(e),"neighborhood",""),Object(o["a"])(Object(g["a"])(e),"park",""),Object(o["a"])(Object(g["a"])(e),"parking",""),Object(o["a"])(Object(g["a"])(e),"point_of_interest",""),Object(o["a"])(Object(g["a"])(e),"political",""),Object(o["a"])(Object(g["a"])(e),"post_box",""),Object(o["a"])(Object(g["a"])(e),"postal_code",""),Object(o["a"])(Object(g["a"])(e),"postal_code_prefix",""),Object(o["a"])(Object(g["a"])(e),"postal_town",""),Object(o["a"])(Object(g["a"])(e),"premise",""),Object(o["a"])(Object(g["a"])(e),"room",""),Object(o["a"])(Object(g["a"])(e),"route",""),Object(o["a"])(Object(g["a"])(e),"street_address",""),Object(o["a"])(Object(g["a"])(e),"street_number",""),Object(o["a"])(Object(g["a"])(e),"sublocality",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_1",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_2",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_3",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_4",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_5",""),Object(o["a"])(Object(g["a"])(e),"subpremise",""),Object(o["a"])(Object(g["a"])(e),"train_station",""),Object(o["a"])(Object(g["a"])(e),"transit_station",""),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_1_short",""),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_2_short",""),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_3_short",""),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_4_short",""),Object(o["a"])(Object(g["a"])(e),"administrative_area_level_5_short",""),Object(o["a"])(Object(g["a"])(e),"airport_short",""),Object(o["a"])(Object(g["a"])(e),"bus_station_short",""),Object(o["a"])(Object(g["a"])(e),"colloquial_area_short",""),Object(o["a"])(Object(g["a"])(e),"establishment_short",""),Object(o["a"])(Object(g["a"])(e),"floor_short",""),Object(o["a"])(Object(g["a"])(e),"intersection_short",""),Object(o["a"])(Object(g["a"])(e),"locality_short",""),Object(o["a"])(Object(g["a"])(e),"natural_feature_short",""),Object(o["a"])(Object(g["a"])(e),"neighborhood_short",""),Object(o["a"])(Object(g["a"])(e),"park_short",""),Object(o["a"])(Object(g["a"])(e),"parking_short",""),Object(o["a"])(Object(g["a"])(e),"point_of_interest_short",""),Object(o["a"])(Object(g["a"])(e),"political_short",""),Object(o["a"])(Object(g["a"])(e),"post_box_short",""),Object(o["a"])(Object(g["a"])(e),"postal_code_short",""),Object(o["a"])(Object(g["a"])(e),"postal_code_prefix_short",""),Object(o["a"])(Object(g["a"])(e),"postal_town_short",""),Object(o["a"])(Object(g["a"])(e),"premise_short",""),Object(o["a"])(Object(g["a"])(e),"room_short",""),Object(o["a"])(Object(g["a"])(e),"route_short",""),Object(o["a"])(Object(g["a"])(e),"street_address_short",""),Object(o["a"])(Object(g["a"])(e),"street_number_short",""),Object(o["a"])(Object(g["a"])(e),"sublocality_short",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_1_short",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_2_short",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_3_short",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_4_short",""),Object(o["a"])(Object(g["a"])(e),"sublocality_level_5_short",""),Object(o["a"])(Object(g["a"])(e),"subpremise_short",""),Object(o["a"])(Object(g["a"])(e),"train_station_short",""),Object(o["a"])(Object(g["a"])(e),"transit_station_short",""),Array.isArray(a)&&(a=a.reduce(function(e,t){var a=t.types[0];return e[a]=t.long_name,e},{})),Object.keys(a).forEach(function(t){e[t]=a[t]}),e}return Object(m["a"])(t,e),t}(d);function y(e,t){if(window.hasOwnProperty(e))t();else var a=setInterval(function(){window.hasOwnProperty(e)&&(t(),clearTimeout(a))})}function w(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),a.push.apply(a,n)}return a}function O(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:null;return window.Craft.t("simplemap",e,t)}var z={props:{geo:x,service:String,defaultValue:String,hasMap:Boolean,size:String},components:{VueAutosuggest:$["a"]},data:function(){return{shouldShow:!1,suggestions:[{data:[]}]}},computed:{cls:function(){var e=[this.$style.wrap];return this.isOpen&&e.push(this.$style.open),this.hasMap||e.push(this.$style["no-map"]),e},inputProps:function(){var e=[this.$style.input];return this.hasMap||e.push(this.$style["no-map"]),{class:e,initialValue:this.initialValue,placeholder:R("Search for a location")}},isOpen:function(){return this.suggestions[0].data.length>0&&this.shouldShow},suggestLengthShow:function(){return"".concat(this.shouldShow).concat(this.suggestions[0].data.length)}},watch:{suggestLengthShow:function(){this.emitHeightChange()}},methods:{shouldRenderSuggestions:function(e,t){return this.shouldShow=e>0&&!t,function(){return!0}},onInputChange:function(e){var t=this;""===e.trim()&&(this.shouldShow=!1),clearTimeout(k),k=setTimeout(Object(s["a"])(regeneratorRuntime.mark(function a(){var n;return regeneratorRuntime.wrap(function(a){while(1)switch(a.prev=a.next){case 0:return a.next=2,t.geo.search(e);case 2:n=a.sent,t.suggestions=[{data:n}],t.emitHeightChange();case 5:case"end":return a.stop()}},a)})),500)},onSelected:function(){var e=Object(s["a"])(regeneratorRuntime.mark(function e(t){var a,n;return regeneratorRuntime.wrap(function(e){while(1)switch(e.prev=e.next){case 0:if(t){e.next=2;break}return e.abrupt("return");case 2:a=t.item,n=a.__placeId,delete a.__placeId,e.t0=this.service,e.next=e.t0===u.GoogleMaps?8:e.t0===u.Here?12:16;break;case 8:return e.next=10,this.geo.getGooglePlaceDetails(n,a);case 10:return a=e.sent,e.abrupt("break",16);case 12:return e.next=14,this.geo.getHerePlaceDetails(n,a);case 14:return a=e.sent,e.abrupt("break",16);case 16:this.$emit("selected",a);case 17:case"end":return e.stop()}},e,this)}));function t(t){return e.apply(this,arguments)}return t}(),renderSuggestion:function(e){var t=e.item;return t.address},getSuggestionValue:function(e){return e.item.address},emitHeightChange:function(){var e=this;if(this.hasMap){var t=this.$refs.self.$el.lastElementChild;this.isOpen&&t.firstElementChild&&!window.matchMedia("(max-width: 767px)").matches?setTimeout(function(){e.$emit("open-offset",t.firstElementChild.getBoundingClientRect().height)}):this.$emit("open-offset",0)}}}},H=z,E=a("1305");function I(e){this["$style"]=E["default"].locals||E["default"]}var T=Object(L["a"])(H,A,D,!1,I,null,null),B=T.exports,N=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("div",{class:e.cls,style:e.styl},[e.showLatLng?a("Fragment",[a("Input",{attrs:{label:e.labels.lat,value:e.value.lat},on:{input:function(t){return e.onInput("lat",t)}}}),a("Input",{attrs:{label:e.labels.lng,value:e.value.lng},on:{input:function(t){return e.onInput("lng",t)}}})],1):e._e(),a("div",{class:[e.$style.full,e.$style.row]},[a("Input",{attrs:{label:e.labels.fullAddress,value:e.value.address,disabled:e.hide},on:{input:function(t){return e.onInput("fullAddress",t)}}}),a("button",{class:e.$style.btn,attrs:{type:"button",title:e.labels.clear},on:{click:function(t){return e.onClear()},mouseenter:e.onDeleteEnter,mouseleave:e.onDeleteLeave}},[a("svg",{attrs:{width:"14",height:"14",viewBox:"0 0 14 14"}},[a("path",{attrs:{fill:"#29323D",d:"M7 14c-3.832 0-7-3.167-7-6.997C0 3.167 3.162 0 6.993 0 10.825 0 14 3.167 14 7.003 14 10.833 10.832 14 7 14zM4.65 9.994a.65.65 0 0 0 .468-.19l1.875-1.88 1.889 1.88c.115.116.27.19.46.19.366 0 .65-.29.65-.65a.674.674 0 0 0-.19-.46l-1.888-1.88 1.889-1.895a.581.581 0 0 0 .196-.447.64.64 0 0 0-.65-.643.59.59 0 0 0-.453.19L6.993 6.097 5.104 4.216a.607.607 0 0 0-.453-.19.64.64 0 0 0-.65.643c0 .17.074.331.196.447L6.08 7.003 4.197 8.898a.608.608 0 0 0-.196.447c0 .358.291.65.65.65z"}})])])],1),e.showW3wField?a("Input",{class:e.$style.full,attrs:{label:e.labels.what3words,value:"/// "+(e.value.what3words||"")},on:{input:e.onW3WInput}}):e._e(),e.hide?e._e():a("Fragment",[a("Input",{attrs:{label:e.labels.number,value:e.value.parts.number},on:{input:function(t){return e.onInput("number",t)}}}),a("Input",{class:e.$style.right,attrs:{label:e.labels.address,value:e.value.parts.address},on:{input:function(t){return e.onInput("address",t)}}}),a("Input",{attrs:{label:e.labels.city,value:e.value.parts.city},on:{input:function(t){return e.onInput("city",t)}}}),a("Input",{class:e.$style.right,attrs:{label:e.labels.postcode,value:e.value.parts.postcode},on:{input:function(t){return e.onInput("postcode",t)}}}),a("Input",{attrs:{label:e.labels.county,value:e.value.parts.county},on:{input:function(t){return e.onInput("county",t)}}}),a("Input",{class:e.$style.right,attrs:{label:e.labels.state,value:e.value.parts.state},on:{input:function(t){return e.onInput("state",t)}}}),a("Input",{attrs:{label:e.labels.country,value:e.value.parts.country},on:{input:function(t){return e.onInput("country",t)}}})],1)],1)},W=[],G=(a("a481"),a("c5f6"),function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("Label",{class:e.className,attrs:{label:e.label}},[a("input",{class:e.$style.input,attrs:{type:e.type,name:e.name,disabled:e.disabled},domProps:{value:e.value},on:{input:function(t){return e.$emit("input",t)}}})])}),Z=[],V=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("label",{class:e.$style.label},[a("span",{class:e.$style.name},[e._v("\n\t\t"+e._s(e.label)+"\n\t")]),e._t("default")],2)},U=[],K={props:{label:String}},F=K,J=a("9e21");function q(e){this["$style"]=J["default"].locals||J["default"]}var Y=Object(L["a"])(F,V,U,!1,q,null,null),Q=Y.exports,X={props:{label:String,name:String,type:{type:String,default:"text"},value:[String,Number],disabled:Boolean,className:Boolean},components:{Label:Q}},ee=X,te=a("1c37");function ae(e){this["$style"]=te["default"].locals||te["default"]}var ne=Object(L["a"])(ee,G,Z,!1,ae,null,null),re=ne.exports,oe={props:{value:{type:Object,default:function(){return{address:"",lat:0,lng:0,parts:{},what3words:""}}},showLatLng:Boolean,fullAddressDirty:Boolean,hide:Boolean,hasSearch:Boolean,hasMap:Boolean,size:String,openOffset:Number,showW3wField:Boolean},components:{Input:re,Fragment:P},data:function(){return{hoverDelete:!1,labels:{fullAddress:R("Full Address"),number:R("Name / Number"),address:R("Street Address"),city:R("Town / City"),postcode:R("Postcode"),county:R("County"),state:R("State"),country:R("Country"),lat:R("Latitude"),lng:R("Longitude"),clear:R("Clear address"),what3words:R("what3words")}}},computed:{cls:function(){var e=[this.$style.grid];return this.hasMap&&this.openOffset>0&&e.push(this.$style.fade),this.hasSearch||e.push(this.$style["no-search"]),this.hasMap||e.push(this.$style["no-map"]),this.hoverDelete&&e.push(this.$style.delete),this.hasValue&&e.push(this.$style["show-clear"]),e},styl:function(){return this.hasMap?{transform:"translateY(".concat(this.openOffset,"px)")}:{}},hasValue:function(){return null!==this.value.address}},methods:{onInput:function(e,t){this.$emit("changed",{name:e,value:t.target.value})},onW3WInput:function(){var e=Object(s["a"])(regeneratorRuntime.mark(function e(t){return regeneratorRuntime.wrap(function(e){while(1)switch(e.prev=e.next){case 0:this.$emit("w3w-change",t.target.value.trim().replace(/\/|\s/g,"").toLowerCase());case 1:case"end":return e.stop()}},e,this)}));function t(t){return e.apply(this,arguments)}return t}(),onClear:function(){this.$emit("clear")},onDeleteEnter:function(){this.hoverDelete=!0},onDeleteLeave:function(){this.hoverDelete=!1}}},se=oe,ie=a("9685");function ce(e){this["$style"]=ie["default"].locals||ie["default"]}var le=Object(L["a"])(se,N,W,!1,ce,null,null),ue=le.exports,pe=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("div",{class:e.cls})},he=[],de=a("e11e"),fe=a.n(de),be=(a("2acb"),a("898b"),{Wikimedia:"wikimedia",OpenStreetMap:"openstreetmap",CartoVoyager:"carto.rastertiles/voyager",CartoPositron:"carto.light_all",CartoDarkMatter:"carto.dark_all",MapboxOutdoors:"mapbox.outdoors",MapboxStreets:"mapbox.streets",MapboxLight:"mapbox.light",MapboxDark:"mapbox.dark",GoogleRoadmap:"google.roadmap",GoogleTerrain:"google.terrain",GoogleHybrid:"google.hybrid",MapKitStandard:"mapkit.standard",MapKitMutedStandard:"mapkit.muted",MapKitSatellite:"mapkit.satellite",MapKitHybrid:"mapkit.hybrid",HereNormalDay:"here.normal.day",HereNormalDayGrey:"here.normal.day.grey",HereNormalDayTransit:"here.normal.day.transit",HereReduced:"here.reduced.day",HerePedestrian:"here.pedestrian.day",HereTerrain:"here.terrain.day",HereSatellite:"here.satellite.day",HereHybrid:"here.hybrid.day"}),ge=be;a("6cc5");function me(e,t){var a;return function(){var n=t.getZoom(),r=n>17;if(r){var o=t.getBounds().getNorthEast(),s=t.getBounds().getSouthWest();window.what3words.api.gridSectionGeoJson({southwest:{lat:s.lat,lng:s.lng},northeast:{lat:o.lat,lng:o.lng}}).then(function(n){a&&t.removeLayer(a),a=e.geoJSON(n,{style:function(){return{color:"#777",stroke:!0,weight:.5}}}).addTo(t)}).catch(console.error)}else a&&t.removeLayer(a)}}function ve(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),a.push.apply(a,n)}return a}function ye(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:"#E7433B";return'')},Oe=function(e,t){return'')},_e={props:{tiles:String,token:[String,Object],latLng:Object,zoom:Number,minZoom:Number,maxZoom:Number,hideSearch:Boolean,hideAddress:Boolean,showCurrentLocation:Boolean,w3wEnabled:Boolean,showW3wGrid:Boolean},data:function(){return{map:null,marker:null}},mounted:function(){var e=this;this.map=fe.a.map(this.$el,{minZoom:this.minZoom,maxZoom:this.maxZoom,scrollWheelZoom:!1,zoomControl:!1}).setView(this.latLng,this.zoom);var t=this;if(fe.a.Control.CustomZoom=fe.a.Control.extend({onAdd:function(a){var n=fe.a.DomUtil.create("div"),r=fe.a.DomUtil.create("button"),o=fe.a.DomUtil.create("button"),s=fe.a.DomUtil.create("button"),i=fe.a.DomUtil.create("button");return n.classList.add(t.$style.control),r.textContent="+",o.textContent="-",s.innerHTML=we(10.5,15,"rgba(41, 50, 61, 0.75)"),i.innerHTML=Oe(15,15),r.setAttribute("title",R("Zoom In")),o.setAttribute("title",R("Zoom Out")),s.setAttribute("title",R("Center on Marker")),i.setAttribute("title",R("Current Location")),n.appendChild(r),n.appendChild(o),n.appendChild(s),e.showCurrentLocation&&"geolocation"in navigator&&n.appendChild(i),r.addEventListener("click",function(e){e.preventDefault(),a.setZoomAround(t.getOffsetPoint(a.getCenter(),!0),a.getZoom()+a.options.zoomDelta)}),o.addEventListener("click",function(e){e.preventDefault(),a.setZoomAround(t.getOffsetPoint(a.getCenter(),!0),a.getZoom()-a.options.zoomDelta)}),s.addEventListener("click",function(e){e.preventDefault(),t.panTo(t.latLng)}),i.addEventListener("click",function(e){e.preventDefault(),navigator.geolocation.getCurrentPosition(function(e){var a=e.coords,n={lat:a.latitude,lng:a.longitude};t.panTo(n),t.marker.setLatLng(n),t.$emit("change",n)},function(e){window.Craft.cp.displayError("Maps: ".concat(e.message))},{enableHighAccuracy:!0})}),n},onRemove:function(){}}),fe.a.control.customZoom=function(e){return new fe.a.Control.CustomZoom(e)},fe.a.control.customZoom({position:"topright"}).addTo(this.map),this.map.panBy(fe.a.point(this.offsetAmount()),{animate:!1}),this.tiles.indexOf("google")>-1)this._googleMutant();else if(this.tiles.indexOf("mapkit")>-1)this._mapKitMutant();else{var a=ye({attribution:this.tileLayer.attr},this.tileLayer.opts||{});this.tileLayer.subdomains&&(a.subdomains=this.tileLayer.subdomains);var n=fe.a.tileLayer(this.tileLayer.url,a);this.map.addLayer(n)}if(this.w3wEnabled&&this.showW3wGrid){var r=me(fe.a,this.map);this.map.whenReady(r),this.map.on("move",r)}this.map.on("zoom",this.onZoom),this.setMarker();var o=new IntersectionObserver(function(t){t[0].intersectionRatio<=0||e.map.invalidateSize(!0)});o.observe(this.$el)},computed:{cls:function(){return[this.$style.map]},tileLayer:function(){var e=fe.a.Browser.retina?"@2x.png":".png",t=fe.a.Browser.retina?"512":"256",a=fe.a.Browser.retina?"@2x":"",n=this.tiles.split(/\.(.+)/)[1];switch(this.tiles){case ge.Wikimedia:return{url:"https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}".concat(e),attr:'© OpenStreetMap, © Wikimedia'};case ge.OpenStreetMap:return{url:"https://tile.openstreetmap.org/{z}/{x}/{y}.png",attr:'© OpenStreetMap'};case ge.CartoVoyager:case ge.CartoPositron:case ge.CartoDarkMatter:return{url:"https://{s}.basemaps.cartocdn.com/".concat(n,"/{z}/{x}/{y}").concat(e),attr:'© OpenStreetMap, © CARTO'};case ge.MapboxOutdoors:case ge.MapboxStreets:case ge.MapboxLight:case ge.MapboxDark:var r="-v11";return this.tiles!==ge.MapboxLight&&this.tiles!==ge.MapboxDark||(r="-v10"),{url:"https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}{scale}?access_token={accessToken}",attr:'© OpenStreetMap, © Mapbox',opts:{accessToken:this.token,id:"mapbox/".concat(n).concat(r),tileSize:512,zoomOffset:-1,scale:a}};case ge.HereNormalDay:case ge.HereNormalDayGrey:case ge.HereNormalDayTransit:case ge.HereReduced:case ge.HerePedestrian:return{url:"https://{s}.base.maps.api.here.com/maptile/2.1/maptile/newest/".concat(n,"/{z}/{x}/{y}/").concat(t,"/png8?app_id=").concat(this.token.appId,"&app_code=").concat(this.token.appCode),attr:'© OpenStreetMap, © Here',subdomains:"1234"};case ge.HereTerrain:case ge.HereSatellite:case ge.HereHybrid:return{url:"https://{s}.aerial.maps.api.here.com/maptile/2.1/maptile/newest/".concat(n,"/{z}/{x}/{y}/").concat(t,"/png8?app_id=").concat(this.token.appId,"&app_code=").concat(this.token.appCode),attr:'© OpenStreetMap, © Here',subdomains:"1234"};default:throw new Error("Unknown map tiles service: "+this.tiles)}},icon:function(){var e=28,t=40;return fe.a.divIcon({html:we(e,t),iconSize:[e,t],iconAnchor:[e/2,t],className:""})}},watch:{latLng:{deep:!0,handler:function(e,t){e.lat===t.lat&&e.lng===t.lng||(this.panTo(this.latLng),this.setMarker())}}},methods:{offsetAmount:function(){var e=0;return window.matchMedia("(max-width: 767px)").matches||(e=-this.$el.getBoundingClientRect().width/4),{x:e,y:this.hideSearch?5:-15}},getOffsetPoint:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],a=this.map.latLngToContainerPoint(e);return t?(a.x-=this.offsetAmount().x,a.y-=this.offsetAmount().y):(a.x+=this.offsetAmount().x,a.y+=this.offsetAmount().y),a},panTo:function(e){this.map.panTo(this.map.containerPointToLatLng(this.getOffsetPoint(e)))},setMarker:function(){var e=this;this.marker&&this.map.removeLayer(this.marker),this.marker=fe.a.marker(this.latLng,{icon:this.icon,draggable:!0,autoPan:!0}),this.map.addLayer(this.marker),this.marker.on("dragend",function(){e.$emit("change",e.marker.getLatLng())})},onZoom:function(){this.$emit("zoom",this.map.getZoom())},_googleMutant:function(){var e=this;y("google",function(){fe.a.gridLayer.googleMutant({type:e.tiles.split(".")[1]}).addTo(e.map)})},_mapKitMutant:function(){var e=this;y("mapkit",function(){fe.a.mapkitMutant({type:e.tiles.split(".")[1],authorizationCallback:function(t){return t(e.token)},language:window.Craft.language}).addTo(e.map)})}}},je=_e,ke=a("f3c8");function xe(e){this["$style"]=ke["default"].locals||ke["default"]}var Se=Object(L["a"])(je,pe,he,!1,xe,null,null),Me=Se.exports;function Le(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),a.push.apply(a,n)}return a}function Ce(e){for(var t=1;to)Y(t,n=i[o++],e[n]);return t},J=function(t,e){return void 0===e?P(t):X(P(t),e)},Q=function(t){var e=N.call(this,t=w(t,!0));return!(this===H&&o(F,t)&&!o(W,t))&&(!(e||!o(this,t)||!o(F,t)||o(this,j)&&this[j][t])||e)},tt=function(t,e){if(t=x(t),e=w(e,!0),t!==H||!o(F,e)||o(W,e)){var n=O(t,e);return!n||!o(F,e)||o(t,j)&&t[j][e]||(n.enumerable=!0),n}},et=function(t){var e,n=I(x(t)),i=[],r=0;while(n.length>r)o(F,e=n[r++])||e==j||e==u||i.push(e);return i},nt=function(t){var e,n=t===H,i=I(n?W:x(t)),r=[],s=0;while(i.length>s)!o(F,e=i[s++])||n&&!o(H,e)||r.push(F[e]);return r};U||(k=function(){if(this instanceof k)throw TypeError("Symbol is not a constructor!");var t=f(arguments.length>0?arguments[0]:void 0),e=function(n){this===H&&e.call(W,n),o(this,j)&&o(this[j],t)&&(this[j][t]=!1),q(this,t,L(1,n))};return r&&G&&q(H,t,{configurable:!0,set:e}),K(t)},a(k[B],"toString",function(){return this._k}),S.f=tt,C.f=Y,n("6abf").f=T.f=et,n("355d").f=Q,M.f=nt,r&&!n("b8e3")&&a(H,"propertyIsEnumerable",Q,!0),p.f=function(t){return K(d(t))}),s(s.G+s.W+s.F*!U,{Symbol:k});for(var it="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),ot=0;it.length>ot;)d(it[ot++]);for(var rt=E(d.store),st=0;rt.length>st;)_(rt[st++]);s(s.S+s.F*!U,"Symbol",{for:function(t){return o(D,t+="")?D[t]:D[t]=k(t)},keyFor:function(t){if(!$(t))throw TypeError(t+" is not a symbol!");for(var e in D)if(D[e]===t)return e},useSetter:function(){G=!0},useSimple:function(){G=!1}}),s(s.S+s.F*!U,"Object",{create:J,defineProperty:Y,defineProperties:X,getOwnPropertyDescriptor:tt,getOwnPropertyNames:et,getOwnPropertySymbols:nt});var at=h(function(){M.f(1)});s(s.S+s.F*at,"Object",{getOwnPropertySymbols:function(t){return M.f(b(t))}}),A&&s(s.S+s.F*(!U||h(function(){var t=k();return"[null]"!=Z([t])||"{}"!=Z({a:t})||"{}"!=Z(Object(t))})),"JSON",{stringify:function(t){var e,n,i=[t],o=1;while(arguments.length>o)i.push(arguments[o++]);if(n=e=i[1],(y(e)||void 0!==t)&&!$(t))return v(e)||(e=function(t,e){if("function"==typeof n&&(e=n.call(this,t,e)),!$(e))return e}),i[1]=e,Z.apply(A,i)}}),k[B][R]||n("35e8")(k[B],R,k[B].valueOf),l(k,"Symbol"),l(Math,"Math",!0),l(i.JSON,"JSON",!0)},"01f9":function(t,e,n){"use strict";var i=n("2d00"),o=n("5ca1"),r=n("2aba"),s=n("32e9"),a=n("84f2"),u=n("41a0"),h=n("7f20"),c=n("38fd"),l=n("2b4c")("iterator"),f=!([].keys&&"next"in[].keys()),d="@@iterator",p="keys",_="values",m=function(){return this};t.exports=function(t,e,n,v,g,y,b){u(n,e,v);var x,w,L,P=function(t){if(!f&&t in C)return C[t];switch(t){case p:return function(){return new n(this,t)};case _:return function(){return new n(this,t)}}return function(){return new n(this,t)}},T=e+" Iterator",S=g==_,M=!1,C=t.prototype,E=C[l]||C[d]||g&&C[g],O=E||P(g),z=g?S?P("entries"):O:void 0,I="Array"==e&&C.entries||E;if(I&&(L=c(I.call(new t)),L!==Object.prototype&&L.next&&(h(L,T,!0),i||"function"==typeof L[l]||s(L,l,m))),S&&E&&E.name!==_&&(M=!0,O=function(){return E.call(this)}),i&&!b||!f&&!M&&C[l]||s(C,l,O),a[e]=O,a[T]=m,g)if(x={values:S?O:P(_),keys:y?O:P(p),entries:z},b)for(w in x)w in C||r(C,w,x[w]);else o(o.P+o.F*(f||M),e,x);return x}},"0293":function(t,e,n){var i=n("241e"),o=n("53e2");n("ce7e")("getPrototypeOf",function(){return function(t){return o(i(t))}})},"02f4":function(t,e,n){var i=n("4588"),o=n("be13");t.exports=function(t){return function(e,n){var r,s,a=String(o(e)),u=i(n),h=a.length;return u<0||u>=h?t?"":void 0:(r=a.charCodeAt(u),r<55296||r>56319||u+1===h||(s=a.charCodeAt(u+1))<56320||s>57343?t?a.charAt(u):r:t?a.slice(u,u+2):s-56320+(r-55296<<10)+65536)}}},"0390":function(t,e,n){"use strict";var i=n("02f4")(!0);t.exports=function(t,e,n){return e+(n?i(t,e).length:1)}},"0395":function(t,e,n){var i=n("36c3"),o=n("6abf").f,r={}.toString,s="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],a=function(t){try{return o(t)}catch(e){return s.slice()}};t.exports.f=function(t){return s&&"[object Window]"==r.call(t)?a(t):o(i(t))}},"061b":function(t,e,n){t.exports=n("fa99")},"07e3":function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},"097d":function(t,e,n){"use strict";var i=n("5ca1"),o=n("8378"),r=n("7726"),s=n("ebd6"),a=n("bcaa");i(i.P+i.R,"Promise",{finally:function(t){var e=s(this,o.Promise||r.Promise),n="function"==typeof t;return this.then(n?function(n){return a(e,t()).then(function(){return n})}:t,n?function(n){return a(e,t()).then(function(){throw n})}:t)}})},"0bfb":function(t,e,n){"use strict";var i=n("cb7c");t.exports=function(){var t=i(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},"0d58":function(t,e,n){var i=n("ce10"),o=n("e11e2");t.exports=Object.keys||function(t){return i(t,o)}},"0fc9":function(t,e,n){var i=n("3a38"),o=Math.max,r=Math.min;t.exports=function(t,e){return t=i(t),t<0?o(t+e,0):r(t,e)}},1173:function(t,e){t.exports=function(t,e,n,i){if(!(t instanceof e)||void 0!==i&&i in t)throw TypeError(n+": incorrect invocation!");return t}},"11e9":function(t,e,n){var i=n("52a7"),o=n("4630"),r=n("6821"),s=n("6a99"),a=n("69a8"),u=n("c69a"),h=Object.getOwnPropertyDescriptor;e.f=n("9e1e")?h:function(t,e){if(t=r(t),e=s(e,!0),u)try{return h(t,e)}catch(n){}if(a(t,e))return o(!i.f.call(t,e),t[e])}},1495:function(t,e,n){var i=n("86cc"),o=n("cb7c"),r=n("0d58");t.exports=n("9e1e")?Object.defineProperties:function(t,e){o(t);var n,s=r(e),a=s.length,u=0;while(a>u)i.f(t,n=s[u++],e[n]);return t}},1654:function(t,e,n){"use strict";var i=n("71c1")(!0);n("30f1")(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,e=this._t,n=this._i;return n>=e.length?{value:void 0,done:!0}:(t=i(e,n),this._i+=t.length,{value:t,done:!1})})},1691:function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},1991:function(t,e,n){var i,o,r,s=n("9b43"),a=n("31f4"),u=n("fab2"),h=n("230e"),c=n("7726"),l=c.process,f=c.setImmediate,d=c.clearImmediate,p=c.MessageChannel,_=c.Dispatch,m=0,v={},g="onreadystatechange",y=function(){var t=+this;if(v.hasOwnProperty(t)){var e=v[t];delete v[t],e()}},b=function(t){y.call(t.data)};f&&d||(f=function(t){var e=[],n=1;while(arguments.length>n)e.push(arguments[n++]);return v[++m]=function(){a("function"==typeof t?t:Function(t),e)},i(m),m},d=function(t){delete v[t]},"process"==n("2d95")(l)?i=function(t){l.nextTick(s(y,t,1))}:_&&_.now?i=function(t){_.now(s(y,t,1))}:p?(o=new p,r=o.port2,o.port1.onmessage=b,i=s(r.postMessage,r,1)):c.addEventListener&&"function"==typeof postMessage&&!c.importScripts?(i=function(t){c.postMessage(t+"","*")},c.addEventListener("message",b,!1)):i=g in h("script")?function(t){u.appendChild(h("script"))[g]=function(){u.removeChild(this),y.call(t)}}:function(t){setTimeout(s(y,t,1),0)}),t.exports={set:f,clear:d}},"1bc3":function(t,e,n){var i=n("f772");t.exports=function(t,e){if(!i(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!i(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!i(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!i(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},"1df8":function(t,e,n){var i=n("63b6");i(i.S,"Object",{setPrototypeOf:n("ead6").set})},"1ec9":function(t,e,n){var i=n("f772"),o=n("e53d").document,r=i(o)&&i(o.createElement);t.exports=function(t){return r?o.createElement(t):{}}},"1fa8":function(t,e,n){var i=n("cb7c");t.exports=function(t,e,n,o){try{return o?e(i(n)[0],n[1]):e(n)}catch(s){var r=t["return"];throw void 0!==r&&i(r.call(t)),s}}},"214f":function(t,e,n){"use strict";n("b0c5");var i=n("2aba"),o=n("32e9"),r=n("79e5"),s=n("be13"),a=n("2b4c"),u=n("520a"),h=a("species"),c=!r(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")}),l=function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2===n.length&&"a"===n[0]&&"b"===n[1]}();t.exports=function(t,e,n){var f=a(t),d=!r(function(){var e={};return e[f]=function(){return 7},7!=""[t](e)}),p=d?!r(function(){var e=!1,n=/a/;return n.exec=function(){return e=!0,null},"split"===t&&(n.constructor={},n.constructor[h]=function(){return n}),n[f](""),!e}):void 0;if(!d||!p||"replace"===t&&!c||"split"===t&&!l){var _=/./[f],m=n(s,f,""[t],function(t,e,n,i,o){return e.exec===u?d&&!o?{done:!0,value:_.call(e,n,i)}:{done:!0,value:t.call(n,e,i)}:{done:!1}}),v=m[0],g=m[1];i(String.prototype,t,v),o(RegExp.prototype,f,2==e?function(t,e){return g.call(t,this,e)}:function(t){return g.call(t,this)})}}},"230e":function(t,e,n){var i=n("d3f4"),o=n("7726").document,r=i(o)&&i(o.createElement);t.exports=function(t){return r?o.createElement(t):{}}},"23c6":function(t,e,n){var i=n("2d95"),o=n("2b4c")("toStringTag"),r="Arguments"==i(function(){return arguments}()),s=function(t,e){try{return t[e]}catch(n){}};t.exports=function(t){var e,n,a;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=s(e=Object(t),o))?n:r?i(e):"Object"==(a=i(e))&&"function"==typeof e.callee?"Arguments":a}},"241e":function(t,e,n){var i=n("25eb");t.exports=function(t){return Object(i(t))}},"24c5":function(t,e,n){"use strict";var i,o,r,s,a=n("b8e3"),u=n("e53d"),h=n("d864"),c=n("40c3"),l=n("63b6"),f=n("f772"),d=n("79aa"),p=n("1173"),_=n("a22a"),m=n("f201"),v=n("4178").set,g=n("aba2")(),y=n("656e"),b=n("4439"),x=n("bc13"),w=n("cd78"),L="Promise",P=u.TypeError,T=u.process,S=T&&T.versions,M=S&&S.v8||"",C=u[L],E="process"==c(T),O=function(){},z=o=y.f,I=!!function(){try{var t=C.resolve(1),e=(t.constructor={})[n("5168")("species")]=function(t){t(O,O)};return(E||"function"==typeof PromiseRejectionEvent)&&t.then(O)instanceof e&&0!==M.indexOf("6.6")&&-1===x.indexOf("Chrome/66")}catch(i){}}(),k=function(t){var e;return!(!f(t)||"function"!=typeof(e=t.then))&&e},A=function(t,e){if(!t._n){t._n=!0;var n=t._c;g(function(){var i=t._v,o=1==t._s,r=0,s=function(e){var n,r,s,a=o?e.ok:e.fail,u=e.resolve,h=e.reject,c=e.domain;try{a?(o||(2==t._h&&j(t),t._h=1),!0===a?n=i:(c&&c.enter(),n=a(i),c&&(c.exit(),s=!0)),n===e.promise?h(P("Promise-chain cycle")):(r=k(n))?r.call(n,u,h):u(n)):h(i)}catch(l){c&&!s&&c.exit(),h(l)}};while(n.length>r)s(n[r++]);t._c=[],t._n=!1,e&&!t._h&&Z(t)})}},Z=function(t){v.call(u,function(){var e,n,i,o=t._v,r=B(t);if(r&&(e=b(function(){E?T.emit("unhandledRejection",o,t):(n=u.onunhandledrejection)?n({promise:t,reason:o}):(i=u.console)&&i.error&&i.error("Unhandled promise rejection",o)}),t._h=E||B(t)?2:1),t._a=void 0,r&&e.e)throw e.v})},B=function(t){return 1!==t._h&&0===(t._a||t._c).length},j=function(t){v.call(u,function(){var e;E?T.emit("rejectionHandled",t):(e=u.onrejectionhandled)&&e({promise:t,reason:t._v})})},R=function(t){var e=this;e._d||(e._d=!0,e=e._w||e,e._v=t,e._s=2,e._a||(e._a=e._c.slice()),A(e,!0))},N=function(t){var e,n=this;if(!n._d){n._d=!0,n=n._w||n;try{if(n===t)throw P("Promise can't be resolved itself");(e=k(t))?g(function(){var i={_w:n,_d:!1};try{e.call(t,h(N,i,1),h(R,i,1))}catch(o){R.call(i,o)}}):(n._v=t,n._s=1,A(n,!1))}catch(i){R.call({_w:n,_d:!1},i)}}};I||(C=function(t){p(this,C,L,"_h"),d(t),i.call(this);try{t(h(N,this,1),h(R,this,1))}catch(e){R.call(this,e)}},i=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1},i.prototype=n("5c95")(C.prototype,{then:function(t,e){var n=z(m(this,C));return n.ok="function"!=typeof t||t,n.fail="function"==typeof e&&e,n.domain=E?T.domain:void 0,this._c.push(n),this._a&&this._a.push(n),this._s&&A(this,!1),n.promise},catch:function(t){return this.then(void 0,t)}}),r=function(){var t=new i;this.promise=t,this.resolve=h(N,t,1),this.reject=h(R,t,1)},y.f=z=function(t){return t===C||t===s?new r(t):o(t)}),l(l.G+l.W+l.F*!I,{Promise:C}),n("45f2")(C,L),n("4c95")(L),s=n("584a")[L],l(l.S+l.F*!I,L,{reject:function(t){var e=z(this),n=e.reject;return n(t),e.promise}}),l(l.S+l.F*(a||!I),L,{resolve:function(t){return w(a&&this===s?C:this,t)}}),l(l.S+l.F*!(I&&n("4ee1")(function(t){C.all(t)["catch"](O)})),L,{all:function(t){var e=this,n=z(e),i=n.resolve,o=n.reject,r=b(function(){var n=[],r=0,s=1;_(t,!1,function(t){var a=r++,u=!1;n.push(void 0),s++,e.resolve(t).then(function(t){u||(u=!0,n[a]=t,--s||i(n))},o)}),--s||i(n)});return r.e&&o(r.v),n.promise},race:function(t){var e=this,n=z(e),i=n.reject,o=b(function(){_(t,!1,function(t){e.resolve(t).then(n.resolve,i)})});return o.e&&i(o.v),n.promise}})},"25b0":function(t,e,n){n("1df8"),t.exports=n("584a").Object.setPrototypeOf},"25eb":function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},2621:function(t,e){e.f=Object.getOwnPropertySymbols},"27ee":function(t,e,n){var i=n("23c6"),o=n("2b4c")("iterator"),r=n("84f2");t.exports=n("8378").getIteratorMethod=function(t){if(void 0!=t)return t[o]||t["@@iterator"]||r[i(t)]}},2831:function(t,e,n){"use strict";n.d(e,"a",function(){return u});var i={name:"default-section",props:{section:{type:Object,required:!0},currentIndex:{type:[Number,String],required:!1,default:1/0},renderSuggestion:{type:Function,required:!1},normalizeItemFunction:{type:Function,required:!0}},data:function(){return{_currentIndex:null}},computed:{list:function(){var t=this.section,e=t.limit,n=t.data;return n.length0&&!e}},sectionConfigs:{type:Object,required:!1,default:function(){return{default:{onSelected:null}}}},onSelected:{type:Function,required:!1,default:null},componentAttrIdAutosuggest:{type:String,required:!1,default:"autosuggest"},componentAttrClassAutosuggestResultsContainer:{type:String,required:!1,default:"autosuggest__results-container"},componentAttrClassAutosuggestResults:{type:String,required:!1,default:"autosuggest__results"}},data:function(){return{internalValue:null,searchInputOriginal:null,currentIndex:null,currentItem:null,loading:!1,didSelectFromOptions:!1,internal_inputProps:{},defaultInputProps:{type:"text",autocomplete:"off"},clientXMouseDownInitial:null}},computed:{listeners:function(){var t=this;return Object.assign({},this.$listeners,{input:function(t){},click:function(){t.loading=!1,t.$listeners.click&&t.$listeners.click(t.currentItem),t.ensureItemVisible(t.currentItem,t.currentIndex)},selected:function(){t.currentItem&&t.sectionConfigs[t.currentItem.name]&&t.sectionConfigs[t.currentItem.name].onSelected?t.sectionConfigs[t.currentItem.name].onSelected(t.currentItem,t.searchInputOriginal):t.sectionConfigs.default.onSelected?t.sectionConfigs.default.onSelected(null,t.searchInputOriginal):t.$listeners.selected&&t.$emit("selected",t.currentItem),t.setChangeItem(null)}})},isOpen:function(){return this.shouldRenderSuggestions(this.totalResults,this.loading)},computedSections:function(){var t=this,e=0;return this.suggestions.map(function(n){if(n.data){var i,o,r=n.name?n.name:a.name,s=null;t.sectionConfigs[r]&&(i=t.sectionConfigs[r].limit,s=t.sectionConfigs[r].type,o=t.sectionConfigs[r].label),s=s||a.type,i=(i=i||t.limit)||1/0,i=n.data.length=n?n:i.length)},0)}},watch:{value:{handler:function(t){this.internalValue=t},immediate:!0}},created:function(){this.internal_inputProps=Object.assign({},this.defaultInputProps,this.inputProps),this.loading=!0},mounted:function(){document.addEventListener("mouseup",this.onDocumentMouseUp),document.addEventListener("mousedown",this.onDocumentMouseDown)},beforeDestroy:function(){document.removeEventListener("mouseup",this.onDocumentMouseUp),document.removeEventListener("mousedown",this.onDocumentMouseDown)},methods:{inputHandler:function(t){var e=t.target.value;this.$emit("input",e),this.internalValue=e,this.didSelectFromOptions||(this.searchInputOriginal=e,this.currentIndex=null)},getSectionRef:function(t){return"computed_section_"+t},getItemByIndex:function(t){var e=!1;if(null===t)return e;for(var n=0;n=this.computedSections[n].start_index&&t<=this.computedSections[n].end_index){var i=t-this.computedSections[n].start_index,o=this.$refs["computed_section_"+n][0];if(o){e=this.normalizeItem(this.computedSections[n].name,this.computedSections[n].type,o.section.label,o.getItemByIndex(i));break}}return e},handleKeyStroke:function(t){var e=t.keyCode;if(!([16,9,18,91,93].indexOf(e)>-1))switch(this.loading=!1,this.didSelectFromOptions=!1,e){case 40:case 38:if(t.preventDefault(),this.isOpen){if(38===e&&null===this.currentIndex)break;var n=40===e?1:-1,i=parseInt(this.currentIndex)+n;this.setCurrentIndex(i,this.totalResults),this.didSelectFromOptions=!0,this.totalResults>0&&this.currentIndex>=0?(this.setChangeItem(this.getItemByIndex(this.currentIndex)),this.didSelectFromOptions=!0):-1==this.currentIndex&&(this.currentIndex=null,this.internalValue=this.searchInputOriginal,t.preventDefault())}break;case 13:t.preventDefault(),this.totalResults>0&&this.currentIndex>=0&&(this.setChangeItem(this.getItemByIndex(this.currentIndex),!0),this.didSelectFromOptions=!0),this.loading=!0,this.listeners.selected(this.didSelectFromOptions);break;case 27:this.isOpen&&(this.loading=!0,this.currentIndex=null,this.internalValue=this.searchInputOriginal,this.$emit("input",this.searchInputOriginal),t.preventDefault())}},setChangeItem:function(t,e){if(void 0===e&&(e=!1),null!==this.currentIndex&&t){if(t){this.currentItem=t;var n=this.getSuggestionValue(t);this.internalValue=n,e&&(this.searchInputOriginal=n),this.ensureItemVisible(t,this.currentIndex)}}else this.currentItem=null},normalizeItem:function(t,e,n,i){return{name:t,type:e,label:n,item:i}},ensureItemVisible:function(t,e){var n=this.$el.querySelector("."+this.componentAttrClassAutosuggestResults);if(t&&(e||0===e)&&n){var i=this.$el.querySelector("#autosuggest__results-item--"+e);if(i){var o=n.clientHeight,r=n.scrollTop,s=i.clientHeight,a=i.offsetTop;s+a>=r+o?n.scrollTop=s+a-o:a0&&(n.scrollTop=a)}}},updateCurrentIndex:function(t){this.setCurrentIndex(t,-1,!0)},clickedOnScrollbar:function(t,e){var n=this.$el.querySelector("."+this.componentAttrClassAutosuggestResults),i=n&&n.clientWidth<=e+17&&e+17<=n.clientWidth+34;return"DIV"===t.target.tagName&&n&&i||!1},onDocumentMouseDown:function(t){var e=t.target.getBoundingClientRect?t.target.getBoundingClientRect():0;this.clientXMouseDownInitial=t.clientX-e.left},onDocumentMouseUp:function(t){if(!this.$el.contains(t.target))return this.loading=!0,void(this.currentIndex=null);"INPUT"===t.target.tagName||this.clickedOnScrollbar(t,this.clientXMouseDownInitial)||(this.loading=!0,this.didSelectFromOptions=!0,this.setChangeItem(this.getItemByIndex(this.currentIndex),!0),this.listeners.selected(!0))},setCurrentIndex:function(t,e,n){void 0===e&&(e=-1),void 0===n&&(n=!1);var i=t;n||(null===this.currentIndex||t>=e)&&(i=0),this.currentIndex=i;var o=this.$el.querySelector("#autosuggest__results-item--"+this.currentIndex),a="autosuggest__results-item--highlighted";this.$el.querySelector("."+a)&&s(this.$el.querySelector("."+a),a),o&&r(o,a)}}},h={install:function(t){t.component("vue-autosuggest-default-section",i),t.component("vue-autosuggest",u)}};"undefined"!=typeof window&&window.Vue&&window.Vue.use(h)},2877:function(t,e,n){"use strict";function i(t,e,n,i,o,r,s,a){var u,h="function"===typeof t?t.options:t;if(e&&(h.render=e,h.staticRenderFns=n,h._compiled=!0),i&&(h.functional=!0),r&&(h._scopeId="data-v-"+r),s?(u=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(s)},h._ssrRegister=u):o&&(u=a?function(){o.call(this,this.$root.$options.shadowRoot)}:o),u)if(h.functional){h._injectStyles=u;var c=h.render;h.render=function(t,e){return u.call(e),c(t,e)}}else{var l=h.beforeCreate;h.beforeCreate=l?[].concat(l,u):[u]}return{exports:t,options:h}}n.d(e,"a",function(){return i})},"28a5":function(t,e,n){"use strict";var i=n("aae3"),o=n("cb7c"),r=n("ebd6"),s=n("0390"),a=n("9def"),u=n("5f1b"),h=n("520a"),c=n("79e5"),l=Math.min,f=[].push,d="split",p="length",_="lastIndex",m=4294967295,v=!c(function(){RegExp(m,"y")});n("214f")("split",2,function(t,e,n,c){var g;return g="c"=="abbc"[d](/(b)*/)[1]||4!="test"[d](/(?:)/,-1)[p]||2!="ab"[d](/(?:ab)*/)[p]||4!="."[d](/(.?)(.?)/)[p]||"."[d](/()()/)[p]>1||""[d](/.?/)[p]?function(t,e){var o=String(this);if(void 0===t&&0===e)return[];if(!i(t))return n.call(o,t,e);var r,s,a,u=[],c=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),l=0,d=void 0===e?m:e>>>0,v=new RegExp(t.source,c+"g");while(r=h.call(v,o)){if(s=v[_],s>l&&(u.push(o.slice(l,r.index)),r[p]>1&&r.index=d))break;v[_]===r.index&&v[_]++}return l===o[p]?!a&&v.test("")||u.push(""):u.push(o.slice(l)),u[p]>d?u.slice(0,d):u}:"0"[d](void 0,0)[p]?function(t,e){return void 0===t&&0===e?[]:n.call(this,t,e)}:n,[function(n,i){var o=t(this),r=void 0==n?void 0:n[e];return void 0!==r?r.call(n,o,i):g.call(String(o),n,i)},function(t,e){var i=c(g,t,this,e,g!==n);if(i.done)return i.value;var h=o(t),f=String(this),d=r(h,RegExp),p=h.unicode,_=(h.ignoreCase?"i":"")+(h.multiline?"m":"")+(h.unicode?"u":"")+(v?"y":"g"),y=new d(v?h:"^(?:"+h.source+")",_),b=void 0===e?m:e>>>0;if(0===b)return[];if(0===f.length)return null===u(y,f)?[f]:[];var x=0,w=0,L=[];while(w=10?(clearInterval(i),e(new Error("window.google not found after 10 attempts"))):window.google&&window.google.maps&&window.google.maps.Map?(clearInterval(i),t(window.google)):void n++},500)}),this._tileCallbacks={},this._freshTiles={},this._imagesPerTile="hybrid"===this.options.type?2:1,this._boundOnMutatedImage=this._onMutatedImage.bind(this)},onAdd:function(t){L.GridLayer.prototype.onAdd.call(this,t),this._initMutantContainer(),this._GAPIPromise.then(function(){if(this._ready=!0,this._map=t,this._initMutant(),t.on("viewreset",this._reset,this),this.options.updateWhenIdle?t.on("moveend",this._update,this):t.on("move",this._update,this),t.on("zoomend",this._handleZoomAnim,this),t.on("resize",this._resize,this),google.maps.event.addListenerOnce(this._mutant,"idle",function(){this._checkZoomLevels(),this._mutantIsReady=!0}.bind(this)),t._controlCorners.bottomright.style.marginBottom="20px",t._controlCorners.bottomleft.style.marginBottom="20px",this._reset(),this._update(),this._subLayers)for(var e in this._subLayers)this._subLayers[e].setMap(this._mutant)}.bind(this))},onRemove:function(t){L.GridLayer.prototype.onRemove.call(this,t),this._observer.disconnect(),t._container.removeChild(this._mutantContainer),google.maps.event.clearListeners(t,"idle"),google.maps.event.clearListeners(this._mutant,"idle"),t.off("viewreset",this._reset,this),t.off("move",this._update,this),t.off("moveend",this._update,this),t.off("zoomend",this._handleZoomAnim,this),t.off("resize",this._resize,this),t._controlCorners&&(t._controlCorners.bottomright.style.marginBottom="0em",t._controlCorners.bottomleft.style.marginBottom="0em")},getAttribution:function(){return this.options.attribution},setElementSize:function(t,e){t.style.width=e.x+"px",t.style.height=e.y+"px"},addGoogleLayer:function(t,e){return this._subLayers||(this._subLayers={}),this._GAPIPromise.then(function(){var n=google.maps[t],i=new n(e);return i.setMap(this._mutant),this._subLayers[t]=i,i}.bind(this))},removeGoogleLayer:function(t){var e=this._subLayers&&this._subLayers[t];e&&(e.setMap(null),delete this._subLayers[t])},_initMutantContainer:function(){this._mutantContainer||(this._mutantContainer=L.DomUtil.create("div","leaflet-google-mutant leaflet-top leaflet-left"),this._mutantContainer.id="_MutantContainer_"+L.Util.stamp(this._mutantContainer),this._mutantContainer.style.zIndex="800",this._mutantContainer.style.pointerEvents="none",L.DomEvent.off(this._mutantContainer)),this._map.getContainer().appendChild(this._mutantContainer),this.setOpacity(this.options.opacity),this.setElementSize(this._mutantContainer,this._map.getSize()),this._attachObserver(this._mutantContainer)},_initMutant:function(){if(this._ready&&this._mutantContainer)if(this._mutant)this._resize();else{this._mutantCenter=new google.maps.LatLng(0,0);var t=new google.maps.Map(this._mutantContainer,{center:this._mutantCenter,zoom:0,tilt:0,mapTypeId:this.options.type,disableDefaultUI:!0,keyboardShortcuts:!1,draggable:!1,disableDoubleClickZoom:!0,scrollwheel:!1,streetViewControl:!1,styles:this.options.styles||{},backgroundColor:"transparent"});this._mutant=t,google.maps.event.addListenerOnce(t,"idle",function(){for(var t=this._mutantContainer.querySelectorAll("a"),e=0;e1&&(t.style.zIndex=1,i=1)):(n=t.src.match(this._satRegexp),n&&(e={x:n[1],y:n[2],z:n[3]}),i=0),e){var o=this._tileCoordsToKey(e);t.style.position="absolute",t.style.visibility="hidden";var r=o+"/"+i;if(this._freshTiles[r]=t,r in this._tileCallbacks&&this._tileCallbacks[r])this._tileCallbacks[r].pop()(t),this._tileCallbacks[r].length||delete this._tileCallbacks[r];else if(this._tiles[o]){var s=this._tiles[o].el,a=0===i?s.firstChild:s.firstChild.nextSibling,u=this._clone(t);s.replaceChild(u,a)}}else t.src.match(this._staticRegExp)&&(t.style.visibility="hidden")},createTile:function(t,e){var n=this._tileCoordsToKey(t),i=L.DomUtil.create("div");i.dataset.pending=this._imagesPerTile,e=e.bind(this,null,i);for(var o=0;othis.options.maxNativeZoom)&&this._setMaxNativeZoom(e)},_setMaxNativeZoom:function(t){t!=this.options.maxNativeZoom&&(this.options.maxNativeZoom=t,this._resetView())},_reset:function(){this._initContainer()},_update:function(){if(this._mutant){var t=this._map.getCenter(),e=new google.maps.LatLng(t.lat,t.lng);this._mutant.setCenter(e);var n=this._map.getZoom(),i=n!==Math.round(n),o=this._mutant.getZoom();i||n==o||(this._mutant.setZoom(n),this._mutantIsReady&&this._checkZoomLevels())}L.GridLayer.prototype._update.call(this)},_resize:function(){var t=this._map.getSize();this._mutantContainer.style.width===t.x&&this._mutantContainer.style.height===t.y||(this.setElementSize(this._mutantContainer,t),this._mutant&&google.maps.event.trigger(this._mutant,"resize"))},_handleZoomAnim:function(){if(this._mutant){var t=this._map.getCenter(),e=new google.maps.LatLng(t.lat,t.lng);this._mutant.setCenter(e),this._mutant.setZoom(Math.round(this._map.getZoom()))}},_removeTile:function(t){if(this._mutant)return setTimeout(this._pruneTile.bind(this,t),1e3),L.GridLayer.prototype._removeTile.call(this,t)},_pruneTile:function(t){for(var e=this._mutant.getZoom(),n=t.split(":")[2],i=this._mutant.getBounds(),o=i.getSouthWest(),r=i.getNorthEast(),s=L.latLngBounds([[o.lat(),o.lng()],[r.lat(),r.lng()]]),a=0;a";e.style.display="none",n("fab2").appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write(o+"script"+s+"document.F=Object"+o+"/script"+s),t.close(),h=t.F;while(i--)delete h[u][r[i]];return h()};t.exports=Object.create||function(t,e){var n;return null!==t?(a[u]=i(t),n=new a,a[u]=null,n[s]=t):n=h(),void 0===e?n:o(n,e)}},"2b4c":function(t,e,n){var i=n("5537")("wks"),o=n("ca5a"),r=n("7726").Symbol,s="function"==typeof r,a=t.exports=function(t){return i[t]||(i[t]=s&&r[t]||(s?r:o)("Symbol."+t))};a.store=i},"2d00":function(t,e){t.exports=!1},"2d95":function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},3024:function(t,e){t.exports=function(t,e,n){var i=void 0===n;switch(e.length){case 0:return i?t():t.call(n);case 1:return i?t(e[0]):t.call(n,e[0]);case 2:return i?t(e[0],e[1]):t.call(n,e[0],e[1]);case 3:return i?t(e[0],e[1],e[2]):t.call(n,e[0],e[1],e[2]);case 4:return i?t(e[0],e[1],e[2],e[3]):t.call(n,e[0],e[1],e[2],e[3])}return t.apply(n,e)}},"308d":function(t,e,n){"use strict";var i=n("5d58"),o=n.n(i),r=n("67bb"),s=n.n(r);function a(t){return a="function"===typeof s.a&&"symbol"===typeof o.a?function(t){return typeof t}:function(t){return t&&"function"===typeof s.a&&t.constructor===s.a&&t!==s.a.prototype?"symbol":typeof t},a(t)}function u(t){return u="function"===typeof s.a&&"symbol"===a(o.a)?function(t){return a(t)}:function(t){return t&&"function"===typeof s.a&&t.constructor===s.a&&t!==s.a.prototype?"symbol":a(t)},u(t)}var h=n("013f");function c(t,e){return!e||"object"!==u(e)&&"function"!==typeof e?Object(h["a"])(t):e}n.d(e,"a",function(){return c})},"30f1":function(t,e,n){"use strict";var i=n("b8e3"),o=n("63b6"),r=n("9138"),s=n("35e8"),a=n("481b"),u=n("8f60"),h=n("45f2"),c=n("53e2"),l=n("5168")("iterator"),f=!([].keys&&"next"in[].keys()),d="@@iterator",p="keys",_="values",m=function(){return this};t.exports=function(t,e,n,v,g,y,b){u(n,e,v);var x,w,L,P=function(t){if(!f&&t in C)return C[t];switch(t){case p:return function(){return new n(this,t)};case _:return function(){return new n(this,t)}}return function(){return new n(this,t)}},T=e+" Iterator",S=g==_,M=!1,C=t.prototype,E=C[l]||C[d]||g&&C[g],O=E||P(g),z=g?S?P("entries"):O:void 0,I="Array"==e&&C.entries||E;if(I&&(L=c(I.call(new t)),L!==Object.prototype&&L.next&&(h(L,T,!0),i||"function"==typeof L[l]||s(L,l,m))),S&&E&&E.name!==_&&(M=!0,O=function(){return E.call(this)}),i&&!b||!f&&!M&&C[l]||s(C,l,O),a[e]=O,a[T]=m,g)if(x={values:S?O:P(_),keys:y?O:P(p),entries:z},b)for(w in x)w in C||r(C,w,x[w]);else o(o.P+o.F*(f||M),e,x);return x}},"31f4":function(t,e){t.exports=function(t,e,n){var i=void 0===n;switch(e.length){case 0:return i?t():t.call(n);case 1:return i?t(e[0]):t.call(n,e[0]);case 2:return i?t(e[0],e[1]):t.call(n,e[0],e[1]);case 3:return i?t(e[0],e[1],e[2]):t.call(n,e[0],e[1],e[2]);case 4:return i?t(e[0],e[1],e[2],e[3]):t.call(n,e[0],e[1],e[2],e[3])}return t.apply(n,e)}},"32e9":function(t,e,n){var i=n("86cc"),o=n("4630");t.exports=n("9e1e")?function(t,e,n){return i.f(t,e,o(1,n))}:function(t,e,n){return t[e]=n,t}},"32fc":function(t,e,n){var i=n("e53d").document;t.exports=i&&i.documentElement},"335c":function(t,e,n){var i=n("6b4c");t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==i(t)?t.split(""):Object(t)}},"33a4":function(t,e,n){var i=n("84f2"),o=n("2b4c")("iterator"),r=Array.prototype;t.exports=function(t){return void 0!==t&&(i.Array===t||r[o]===t)}},"355d":function(t,e){e.f={}.propertyIsEnumerable},"35e8":function(t,e,n){var i=n("d9f6"),o=n("aebd");t.exports=n("8e60")?function(t,e,n){return i.f(t,e,o(1,n))}:function(t,e,n){return t[e]=n,t}},"36c3":function(t,e,n){var i=n("335c"),o=n("25eb");t.exports=function(t){return i(o(t))}},3702:function(t,e,n){var i=n("481b"),o=n("5168")("iterator"),r=Array.prototype;t.exports=function(t){return void 0!==t&&(i.Array===t||r[o]===t)}},3846:function(t,e,n){n("9e1e")&&"g"!=/./g.flags&&n("86cc").f(RegExp.prototype,"flags",{configurable:!0,get:n("0bfb")})},"386d":function(t,e,n){"use strict";var i=n("cb7c"),o=n("83a1"),r=n("5f1b");n("214f")("search",1,function(t,e,n,s){return[function(n){var i=t(this),o=void 0==n?void 0:n[e];return void 0!==o?o.call(n,i):new RegExp(n)[e](String(i))},function(t){var e=s(n,t,this);if(e.done)return e.value;var a=i(t),u=String(this),h=a.lastIndex;o(h,0)||(a.lastIndex=0);var c=r(a,u);return o(a.lastIndex,h)||(a.lastIndex=h),null===c?-1:c.index}]})},"38fd":function(t,e,n){var i=n("69a8"),o=n("4bf8"),r=n("613b")("IE_PROTO"),s=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=o(t),i(t,r)?t[r]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?s:null}},"3a38":function(t,e){var n=Math.ceil,i=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?i:n)(t)}},"3b8d":function(t,e,n){"use strict";n.d(e,"a",function(){return s});var i=n("795b"),o=n.n(i);function r(t,e,n,i,r,s,a){try{var u=t[s](a),h=u.value}catch(c){return void n(c)}u.done?e(h):o.a.resolve(h).then(i,r)}function s(t){return function(){var e=this,n=arguments;return new o.a(function(i,o){var s=t.apply(e,n);function a(t){r(s,i,o,a,u,"next",t)}function u(t){r(s,i,o,a,u,"throw",t)}a(void 0)})}}},"3c11":function(t,e,n){"use strict";var i=n("63b6"),o=n("584a"),r=n("e53d"),s=n("f201"),a=n("cd78");i(i.P+i.R,"Promise",{finally:function(t){var e=s(this,o.Promise||r.Promise),n="function"==typeof t;return this.then(n?function(n){return a(e,t()).then(function(){return n})}:t,n?function(n){return a(e,t()).then(function(){throw n})}:t)}})},"40c3":function(t,e,n){var i=n("6b4c"),o=n("5168")("toStringTag"),r="Arguments"==i(function(){return arguments}()),s=function(t,e){try{return t[e]}catch(n){}};t.exports=function(t){var e,n,a;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=s(e=Object(t),o))?n:r?i(e):"Object"==(a=i(e))&&"function"==typeof e.callee?"Arguments":a}},4178:function(t,e,n){var i,o,r,s=n("d864"),a=n("3024"),u=n("32fc"),h=n("1ec9"),c=n("e53d"),l=c.process,f=c.setImmediate,d=c.clearImmediate,p=c.MessageChannel,_=c.Dispatch,m=0,v={},g="onreadystatechange",y=function(){var t=+this;if(v.hasOwnProperty(t)){var e=v[t];delete v[t],e()}},b=function(t){y.call(t.data)};f&&d||(f=function(t){var e=[],n=1;while(arguments.length>n)e.push(arguments[n++]);return v[++m]=function(){a("function"==typeof t?t:Function(t),e)},i(m),m},d=function(t){delete v[t]},"process"==n("6b4c")(l)?i=function(t){l.nextTick(s(y,t,1))}:_&&_.now?i=function(t){_.now(s(y,t,1))}:p?(o=new p,r=o.port2,o.port1.onmessage=b,i=s(r.postMessage,r,1)):c.addEventListener&&"function"==typeof postMessage&&!c.importScripts?(i=function(t){c.postMessage(t+"","*")},c.addEventListener("message",b,!1)):i=g in h("script")?function(t){u.appendChild(h("script"))[g]=function(){u.removeChild(this),y.call(t)}}:function(t){setTimeout(s(y,t,1),0)}),t.exports={set:f,clear:d}},"41a0":function(t,e,n){"use strict";var i=n("2aeb"),o=n("4630"),r=n("7f20"),s={};n("32e9")(s,n("2b4c")("iterator"),function(){return this}),t.exports=function(t,e,n){t.prototype=i(s,{next:o(1,n)}),r(t,e+" Iterator")}},"43fc":function(t,e,n){"use strict";var i=n("63b6"),o=n("656e"),r=n("4439");i(i.S,"Promise",{try:function(t){var e=o.f(this),n=r(t);return(n.e?e.reject:e.resolve)(n.v),e.promise}})},4439:function(t,e){t.exports=function(t){try{return{e:!1,v:t()}}catch(e){return{e:!0,v:e}}}},"454f":function(t,e,n){n("46a7");var i=n("584a").Object;t.exports=function(t,e,n){return i.defineProperty(t,e,n)}},"456d":function(t,e,n){var i=n("4bf8"),o=n("0d58");n("5eda")("keys",function(){return function(t){return o(i(t))}})},4588:function(t,e){var n=Math.ceil,i=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?i:n)(t)}},"45f2":function(t,e,n){var i=n("d9f6").f,o=n("07e3"),r=n("5168")("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,r)&&i(t,r,{configurable:!0,value:e})}},4630:function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},"46a7":function(t,e,n){var i=n("63b6");i(i.S+i.F*!n("8e60"),"Object",{defineProperty:n("d9f6").f})},"47ee":function(t,e,n){var i=n("c3a1"),o=n("9aa9"),r=n("355d");t.exports=function(t){var e=i(t),n=o.f;if(n){var s,a=n(t),u=r.f,h=0;while(a.length>h)u.call(t,s=a[h++])&&e.push(s)}return e}},"481b":function(t,e){t.exports={}},"4a59":function(t,e,n){var i=n("9b43"),o=n("1fa8"),r=n("33a4"),s=n("cb7c"),a=n("9def"),u=n("27ee"),h={},c={};e=t.exports=function(t,e,n,l,f){var d,p,_,m,v=f?function(){return t}:u(t),g=i(n,l,e?2:1),y=0;if("function"!=typeof v)throw TypeError(t+" is not iterable!");if(r(v)){for(d=a(t.length);d>y;y++)if(m=e?g(s(p=t[y])[0],p[1]):g(t[y]),m===h||m===c)return m}else for(_=v.call(t);!(p=_.next()).done;)if(m=o(_,g,p.value,e),m===h||m===c)return m};e.BREAK=h,e.RETURN=c},"4aa6":function(t,e,n){t.exports=n("dc62")},"4bf8":function(t,e,n){var i=n("be13");t.exports=function(t){return Object(i(t))}},"4c95":function(t,e,n){"use strict";var i=n("e53d"),o=n("584a"),r=n("d9f6"),s=n("8e60"),a=n("5168")("species");t.exports=function(t){var e="function"==typeof o[t]?o[t]:i[t];s&&e&&!e[a]&&r.f(e,a,{configurable:!0,get:function(){return this}})}},"4d16":function(t,e,n){t.exports=n("25b0")},"4e2b":function(t,e,n){"use strict";var i=n("4aa6"),o=n.n(i),r=n("4d16"),s=n.n(r);function a(t,e){return a=s.a||function(t,e){return t.__proto__=e,t},a(t,e)}function u(t,e){if("function"!==typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=o()(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&a(t,e)}n.d(e,"a",function(){return u})},"4ee1":function(t,e,n){var i=n("5168")("iterator"),o=!1;try{var r=[7][i]();r["return"]=function(){o=!0},Array.from(r,function(){throw 2})}catch(s){}t.exports=function(t,e){if(!e&&!o)return!1;var n=!1;try{var r=[7],a=r[i]();a.next=function(){return{done:n=!0}},r[i]=function(){return a},t(r)}catch(s){}return n}},"50ed":function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},5168:function(t,e,n){var i=n("dbdb")("wks"),o=n("62a0"),r=n("e53d").Symbol,s="function"==typeof r,a=t.exports=function(t){return i[t]||(i[t]=s&&r[t]||(s?r:o)("Symbol."+t))};a.store=i},"520a":function(t,e,n){"use strict";var i=n("0bfb"),o=RegExp.prototype.exec,r=String.prototype.replace,s=o,a="lastIndex",u=function(){var t=/a/,e=/b*/g;return o.call(t,"a"),o.call(e,"a"),0!==t[a]||0!==e[a]}(),h=void 0!==/()??/.exec("")[1],c=u||h;c&&(s=function(t){var e,n,s,c,l=this;return h&&(n=new RegExp("^"+l.source+"$(?!\\s)",i.call(l))),u&&(e=l[a]),s=o.call(l,t),u&&s&&(l[a]=l.global?s.index+s[0].length:e),h&&s&&s.length>1&&r.call(s[0],n,function(){for(c=1;cr)s(n[r++]);t._c=[],t._n=!1,e&&!t._h&&Z(t)})}},Z=function(t){v.call(u,function(){var e,n,i,o=t._v,r=B(t);if(r&&(e=b(function(){E?T.emit("unhandledRejection",o,t):(n=u.onunhandledrejection)?n({promise:t,reason:o}):(i=u.console)&&i.error&&i.error("Unhandled promise rejection",o)}),t._h=E||B(t)?2:1),t._a=void 0,r&&e.e)throw e.v})},B=function(t){return 1!==t._h&&0===(t._a||t._c).length},j=function(t){v.call(u,function(){var e;E?T.emit("rejectionHandled",t):(e=u.onrejectionhandled)&&e({promise:t,reason:t._v})})},R=function(t){var e=this;e._d||(e._d=!0,e=e._w||e,e._v=t,e._s=2,e._a||(e._a=e._c.slice()),A(e,!0))},N=function(t){var e,n=this;if(!n._d){n._d=!0,n=n._w||n;try{if(n===t)throw P("Promise can't be resolved itself");(e=k(t))?g(function(){var i={_w:n,_d:!1};try{e.call(t,h(N,i,1),h(R,i,1))}catch(o){R.call(i,o)}}):(n._v=t,n._s=1,A(n,!1))}catch(i){R.call({_w:n,_d:!1},i)}}};I||(C=function(t){p(this,C,L,"_h"),d(t),i.call(this);try{t(h(N,this,1),h(R,this,1))}catch(e){R.call(this,e)}},i=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1},i.prototype=n("dcbc")(C.prototype,{then:function(t,e){var n=z(m(this,C));return n.ok="function"!=typeof t||t,n.fail="function"==typeof e&&e,n.domain=E?T.domain:void 0,this._c.push(n),this._a&&this._a.push(n),this._s&&A(this,!1),n.promise},catch:function(t){return this.then(void 0,t)}}),r=function(){var t=new i;this.promise=t,this.resolve=h(N,t,1),this.reject=h(R,t,1)},y.f=z=function(t){return t===C||t===s?new r(t):o(t)}),l(l.G+l.W+l.F*!I,{Promise:C}),n("7f20")(C,L),n("7a56")(L),s=n("8378")[L],l(l.S+l.F*!I,L,{reject:function(t){var e=z(this),n=e.reject;return n(t),e.promise}}),l(l.S+l.F*(a||!I),L,{resolve:function(t){return w(a&&this===s?C:this,t)}}),l(l.S+l.F*!(I&&n("5cc5")(function(t){C.all(t)["catch"](O)})),L,{all:function(t){var e=this,n=z(e),i=n.resolve,o=n.reject,r=b(function(){var n=[],r=0,s=1;_(t,!1,function(t){var a=r++,u=!1;n.push(void 0),s++,e.resolve(t).then(function(t){u||(u=!0,n[a]=t,--s||i(n))},o)}),--s||i(n)});return r.e&&o(r.v),n.promise},race:function(t){var e=this,n=z(e),i=n.reject,o=b(function(){_(t,!1,function(t){e.resolve(t).then(n.resolve,i)})});return o.e&&i(o.v),n.promise}})},5537:function(t,e,n){var i=n("8378"),o=n("7726"),r="__core-js_shared__",s=o[r]||(o[r]={});(t.exports=function(t,e){return s[t]||(s[t]=void 0!==e?e:{})})("versions",[]).push({version:i.version,mode:n("2d00")?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},5559:function(t,e,n){var i=n("dbdb")("keys"),o=n("62a0");t.exports=function(t){return i[t]||(i[t]=o(t))}},"584a":function(t,e){var n=t.exports={version:"2.6.9"};"number"==typeof __e&&(__e=n)},"5b4e":function(t,e,n){var i=n("36c3"),o=n("b447"),r=n("0fc9");t.exports=function(t){return function(e,n,s){var a,u=i(e),h=o(u.length),c=r(s,h);if(t&&n!=n){while(h>c)if(a=u[c++],a!=a)return!0}else for(;h>c;c++)if((t||c in u)&&u[c]===n)return t||c||0;return!t&&-1}}},"5c95":function(t,e,n){var i=n("35e8");t.exports=function(t,e,n){for(var o in e)n&&t[o]?t[o]=e[o]:i(t,o,e[o]);return t}},"5ca1":function(t,e,n){var i=n("7726"),o=n("8378"),r=n("32e9"),s=n("2aba"),a=n("9b43"),u="prototype",h=function(t,e,n){var c,l,f,d,p=t&h.F,_=t&h.G,m=t&h.S,v=t&h.P,g=t&h.B,y=_?i:m?i[e]||(i[e]={}):(i[e]||{})[u],b=_?o:o[e]||(o[e]={}),x=b[u]||(b[u]={});for(c in _&&(n=e),n)l=!p&&y&&void 0!==y[c],f=(l?y:n)[c],d=g&&l?a(f,i):v&&"function"==typeof f?a(Function.call,f):f,y&&s(y,c,f,t&h.U),b[c]!=f&&r(b,c,d),v&&x[c]!=f&&(x[c]=f)};i.core=o,h.F=1,h.G=2,h.S=4,h.P=8,h.B=16,h.W=32,h.U=64,h.R=128,t.exports=h},"5cc5":function(t,e,n){var i=n("2b4c")("iterator"),o=!1;try{var r=[7][i]();r["return"]=function(){o=!0},Array.from(r,function(){throw 2})}catch(s){}t.exports=function(t,e){if(!e&&!o)return!1;var n=!1;try{var r=[7],a=r[i]();a.next=function(){return{done:n=!0}},r[i]=function(){return a},t(r)}catch(s){}return n}},"5d58":function(t,e,n){t.exports=n("d8d6")},"5dbc":function(t,e,n){var i=n("d3f4"),o=n("8b97").set;t.exports=function(t,e,n){var r,s=e.constructor;return s!==n&&"function"==typeof s&&(r=s.prototype)!==n.prototype&&i(r)&&o&&o(t,r),t}},"5eda":function(t,e,n){var i=n("5ca1"),o=n("8378"),r=n("79e5");t.exports=function(t,e){var n=(o.Object||{})[t]||Object[t],s={};s[t]=e(n),i(i.S+i.F*r(function(){n(1)}),"Object",s)}},"5f1b":function(t,e,n){"use strict";var i=n("23c6"),o=RegExp.prototype.exec;t.exports=function(t,e){var n=t.exec;if("function"===typeof n){var r=n.call(t,e);if("object"!==typeof r)throw new TypeError("RegExp exec method returned something other than an Object or null");return r}if("RegExp"!==i(t))throw new TypeError("RegExp#exec called on incompatible receiver");return o.call(t,e)}},"613b":function(t,e,n){var i=n("5537")("keys"),o=n("ca5a");t.exports=function(t){return i[t]||(i[t]=o(t))}},"626a":function(t,e,n){var i=n("2d95");t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==i(t)?t.split(""):Object(t)}},"62a0":function(t,e){var n=0,i=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+i).toString(36))}},"63b6":function(t,e,n){var i=n("e53d"),o=n("584a"),r=n("d864"),s=n("35e8"),a=n("07e3"),u="prototype",h=function(t,e,n){var c,l,f,d=t&h.F,p=t&h.G,_=t&h.S,m=t&h.P,v=t&h.B,g=t&h.W,y=p?o:o[e]||(o[e]={}),b=y[u],x=p?i:_?i[e]:(i[e]||{})[u];for(c in p&&(n=e),n)l=!d&&x&&void 0!==x[c],l&&a(y,c)||(f=l?x[c]:n[c],y[c]=p&&"function"!=typeof x[c]?n[c]:v&&l?r(f,i):g&&x[c]==f?function(t){var e=function(e,n,i){if(this instanceof t){switch(arguments.length){case 0:return new t;case 1:return new t(e);case 2:return new t(e,n)}return new t(e,n,i)}return t.apply(this,arguments)};return e[u]=t[u],e}(f):m&&"function"==typeof f?r(Function.call,f):f,m&&((y.virtual||(y.virtual={}))[c]=f,t&h.R&&b&&!b[c]&&s(b,c,f)))};h.F=1,h.G=2,h.S=4,h.P=8,h.B=16,h.W=32,h.U=64,h.R=128,t.exports=h},"656e":function(t,e,n){"use strict";var i=n("79aa");function o(t){var e,n;this.promise=new t(function(t,i){if(void 0!==e||void 0!==n)throw TypeError("Bad Promise constructor");e=t,n=i}),this.resolve=i(e),this.reject=i(n)}t.exports.f=function(t){return new o(t)}},6718:function(t,e,n){var i=n("e53d"),o=n("584a"),r=n("b8e3"),s=n("ccb9"),a=n("d9f6").f;t.exports=function(t){var e=o.Symbol||(o.Symbol=r?{}:i.Symbol||{});"_"==t.charAt(0)||t in e||a(e,t,{value:s.f(t)})}},"67bb":function(t,e,n){t.exports=n("f921")},6821:function(t,e,n){var i=n("626a"),o=n("be13");t.exports=function(t){return i(o(t))}},"696e":function(t,e,n){n("c207"),n("1654"),n("6c1c"),n("24c5"),n("3c11"),n("43fc"),t.exports=n("584a").Promise},"69a8":function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},"69d3":function(t,e,n){n("6718")("asyncIterator")},"6a99":function(t,e,n){var i=n("d3f4");t.exports=function(t,e){if(!i(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!i(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!i(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!i(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},"6abf":function(t,e,n){var i=n("e6f3"),o=n("1691").concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return i(t,o)}},"6b4c":function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},"6b54":function(t,e,n){"use strict";n("3846");var i=n("cb7c"),o=n("0bfb"),r=n("9e1e"),s="toString",a=/./[s],u=function(t){n("2aba")(RegExp.prototype,s,t,!0)};n("79e5")(function(){return"/a/b"!=a.call({source:"a",flags:"b"})})?u(function(){var t=i(this);return"/".concat(t.source,"/","flags"in t?t.flags:!r&&t instanceof RegExp?o.call(t):void 0)}):a.name!=s&&u(function(){return a.call(this)})},"6bb5":function(t,e,n){"use strict";n.d(e,"a",function(){return a});var i=n("061b"),o=n.n(i),r=n("4d16"),s=n.n(r);function a(t){return a=s.a?o.a:function(t){return t.__proto__||o()(t)},a(t)}},"6c1c":function(t,e,n){n("c367");for(var i=n("e53d"),o=n("35e8"),r=n("481b"),s=n("5168")("toStringTag"),a="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),u=0;u=h?t?"":void 0:(r=a.charCodeAt(u),r<55296||r>56319||u+1===h||(s=a.charCodeAt(u+1))<56320||s>57343?t?a.charAt(u):r:t?a.slice(u,u+2):s-56320+(r-55296<<10)+65536)}}},7333:function(t,e,n){"use strict";var i=n("9e1e"),o=n("0d58"),r=n("2621"),s=n("52a7"),a=n("4bf8"),u=n("626a"),h=Object.assign;t.exports=!h||n("79e5")(function(){var t={},e={},n=Symbol(),i="abcdefghijklmnopqrst";return t[n]=7,i.split("").forEach(function(t){e[t]=t}),7!=h({},t)[n]||Object.keys(h({},e)).join("")!=i})?function(t,e){var n=a(t),h=arguments.length,c=1,l=r.f,f=s.f;while(h>c){var d,p=u(arguments[c++]),_=l?o(p).concat(l(p)):o(p),m=_.length,v=0;while(m>v)d=_[v++],i&&!f.call(p,d)||(n[d]=p[d])}return n}:h},"765d":function(t,e,n){n("6718")("observable")},7726:function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},"77f1":function(t,e,n){var i=n("4588"),o=Math.max,r=Math.min;t.exports=function(t,e){return t=i(t),t<0?o(t+e,0):r(t,e)}},"794b":function(t,e,n){t.exports=!n("8e60")&&!n("294c")(function(){return 7!=Object.defineProperty(n("1ec9")("div"),"a",{get:function(){return 7}}).a})},"795b":function(t,e,n){t.exports=n("696e")},"79aa":function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},"79e5":function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},"7a56":function(t,e,n){"use strict";var i=n("7726"),o=n("86cc"),r=n("9e1e"),s=n("2b4c")("species");t.exports=function(t){var e=i[t];r&&e&&!e[s]&&o.f(e,s,{configurable:!0,get:function(){return this}})}},"7cd6":function(t,e,n){var i=n("40c3"),o=n("5168")("iterator"),r=n("481b");t.exports=n("584a").getIteratorMethod=function(t){if(void 0!=t)return t[o]||t["@@iterator"]||r[i(t)]}},"7e90":function(t,e,n){var i=n("d9f6"),o=n("e4ae"),r=n("c3a1");t.exports=n("8e60")?Object.defineProperties:function(t,e){o(t);var n,s=r(e),a=s.length,u=0;while(a>u)i.f(t,n=s[u++],e[n]);return t}},"7f20":function(t,e,n){var i=n("86cc").f,o=n("69a8"),r=n("2b4c")("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,r)&&i(t,r,{configurable:!0,value:e})}},"7f7f":function(t,e,n){var i=n("86cc").f,o=Function.prototype,r=/^\s*function ([^ (]*)/,s="name";s in o||n("9e1e")&&i(o,s,{configurable:!0,get:function(){try{return(""+this).match(r)[1]}catch(t){return""}}})},8079:function(t,e,n){var i=n("7726"),o=n("1991").set,r=i.MutationObserver||i.WebKitMutationObserver,s=i.process,a=i.Promise,u="process"==n("2d95")(s);t.exports=function(){var t,e,n,h=function(){var i,o;u&&(i=s.domain)&&i.exit();while(t){o=t.fn,t=t.next;try{o()}catch(r){throw t?n():e=void 0,r}}e=void 0,i&&i.enter()};if(u)n=function(){s.nextTick(h)};else if(!r||i.navigator&&i.navigator.standalone)if(a&&a.resolve){var c=a.resolve(void 0);n=function(){c.then(h)}}else n=function(){o.call(i,h)};else{var l=!0,f=document.createTextNode("");new r(h).observe(f,{characterData:!0}),n=function(){f.data=l=!l}}return function(i){var o={fn:i,next:void 0};e&&(e.next=o),t||(t=o,n()),e=o}}},8378:function(t,e){var n=t.exports={version:"2.6.9"};"number"==typeof __e&&(__e=n)},"83a1":function(t,e){t.exports=Object.is||function(t,e){return t===e?0!==t||1/t===1/e:t!=t&&e!=e}},8436:function(t,e){t.exports=function(){}},"84f2":function(t,e){t.exports={}},"85f2":function(t,e,n){t.exports=n("454f")},"86cc":function(t,e,n){var i=n("cb7c"),o=n("c69a"),r=n("6a99"),s=Object.defineProperty;e.f=n("9e1e")?Object.defineProperty:function(t,e,n){if(i(t),e=r(e,!0),i(n),o)try{return s(t,e,n)}catch(a){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},"898b":function(t,e){L.MapkitMutant=L.Layer.extend({options:{minZoom:3,maxZoom:23,type:"standard",authorizationCallback:function(){},opacity:1,debugRectangle:!1},initialize:function(t){L.Util.setOptions(this,t),mapkit.init({authorizationCallback:this.options.authorizationCallback,language:this.options.langhage})},onAdd:function(t){this._map=t,this._initMutantContainer(),this._initMutant(),t.on("move zoom moveend zoomend",this._update,this),t.on("resize",this._resize,this),this._resize()},onRemove:function(t){t._container.removeChild(this._mutantContainer),this._mutantContainer=void 0,t.off("move zoom moveend zoomend",this._update,this),t.off("resize",this._resize,this),this._mutant.removeEventListener("region-change-end",this._onRegionChangeEnd,this),this._canvasOverlay&&this._canvasOverlay.remove()},_initMutantContainer:function(){this._mutantContainer||(this._mutantContainer=L.DomUtil.create("div","leaflet-mapkit-mutant leaflet-top leaflet-left"),this._mutantContainer.id="_MutantContainer_"+L.Util.stamp(this._mutantContainer),this._mutantContainer.style.zIndex="200",this._mutantContainer.style.pointerEvents="none",this._map.getContainer().appendChild(this._mutantContainer)),this.setElementSize(this._mutantContainer,this._map.getSize())},_initMutant:function(){if(this._mutantContainer){var t=mapkit.Map.MapTypes.Standard;"hybrid"===this.options.type?t=mapkit.Map.MapTypes.Hybrid:"satellite"===this.options.type?t=mapkit.Map.MapTypes.Satellite:"muted"===this.options.type&&(t=mapkit.Map.MapTypes.MutedStandard);var e=new mapkit.Map(this._mutantContainer,{visibleMapRect:this._leafletBoundsToMapkitRect(),showsUserLocation:!1,showsUserLocationControl:!1,showsCompass:"hidden",showsZoomControl:!1,showsUserLocationControl:!1,showsScale:!1,showsMapTypeControl:!1,mapType:t});this._mutant=e,e.addEventListener("region-change-end",this._onRegionChangeEnd,this),e.addEventListener("region-change-start",this._onRegionChangeStart,this),this.fire("spawned",{mapObject:e}),L.Util.requestAnimFrame(this._update,this)}},_leafletBoundsToMapkitRect:function(){var t=this._map.getPixelBounds(),e=this._map.options.crs.scale(this._map.getZoom()),n=t.getTopLeft().divideBy(e),i=t.getBottomRight().divideBy(e),o=L.bounds([n,i]),r=o.getCenter(),s=o.getSize(),a=new mapkit.MapRect(r.x-s.x/2,r.y-s.y/2,s.x,s.y);return a},_mapkitRectToLeafletBounds:function(t){var e=new mapkit.MapPoint(t.minX(),t.maxY()).toCoordinate(),n=new mapkit.MapPoint(t.maxX(),t.minY()).toCoordinate(),i=e.longitude+360*Math.floor(t.minX()),o=n.longitude+360*Math.floor(t.maxX()),r=this._map.getCenter().lng;if(ro){s=360*Math.ceil((r-o)/360);i+=s,o+=s}return L.latLngBounds([L.latLng(e.latitude,i),L.latLng(n.latitude,o)])},_update:function(){this._map&&this._mutant&&this._mutant.setVisibleMapRectAnimated(this._leafletBoundsToMapkitRect(),!1)},_resize:function(){var t=this._map.getSize();this._mutantContainer.style.width===t.x&&this._mutantContainer.style.height===t.y||(this.setElementSize(this._mutantContainer,t),this._mutant)},_onRegionChangeEnd:function(t){if(this._mutantCanvas||(this._mutantCanvas=this._mutantContainer.querySelector("canvas.syrup-canvas")),this._map&&this._mutantCanvas){var e=this._mapkitRectToLeafletBounds(this._mutant.visibleMapRect);L.Util.cancelAnimFrame(this._requestedFrame),this._requestedFrame=L.Util.requestAnimFrame(function(){if(this._canvasOverlay)this._canvasOverlay.setBounds(e);else{this._canvasOverlay=L.imageOverlay(null,e);var t=this._canvasOverlay._image=L.DomUtil.create("div");L.DomUtil.addClass(t,"leaflet-image-layer"),L.DomUtil.addClass(t,"leaflet-zoom-animated"),this._mutantCanvas.parentElement.removeChild(this._mutantCanvas),t.appendChild(this._mutantCanvas),this._canvasOverlay.addTo(this._map),this._updateOpacity()}this._mutantCanvas.style.width="100%",this._mutantCanvas.style.height="100%",this._mutantCanvas.style.position="absolute",this.options.debugRectangle&&(this.rectangle?this.rectangle.setBounds(e):this.rectangle=L.rectangle(e,{fill:!1}).addTo(this._map))},this)}},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},_updateOpacity:function(){this._mutantCanvas&&L.DomUtil.setOpacity(this._mutantCanvas,this.options.opacity)},_onRegionChangeStart:function(t){},setElementSize:function(t,e){t.style.width=e.x+"px",t.style.height=e.y+"px"}}),L.mapkitMutant=function(t){return new L.MapkitMutant(t)}},"8b97":function(t,e,n){var i=n("d3f4"),o=n("cb7c"),r=function(t,e){if(o(t),!i(e)&&null!==e)throw TypeError(e+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,e,i){try{i=n("9b43")(Function.call,n("11e9").f(Object.prototype,"__proto__").set,2),i(t,[]),e=!(t instanceof Array)}catch(o){e=!0}return function(t,n){return r(t,n),e?t.__proto__=n:i(t,n),t}}({},!1):void 0),check:r}},"8e60":function(t,e,n){t.exports=!n("294c")(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},"8e6e":function(t,e,n){var i=n("5ca1"),o=n("990b"),r=n("6821"),s=n("11e9"),a=n("f1ae");i(i.S,"Object",{getOwnPropertyDescriptors:function(t){var e,n,i=r(t),u=s.f,h=o(i),c={},l=0;while(h.length>l)n=u(i,e=h[l++]),void 0!==n&&a(c,e,n);return c}})},"8f60":function(t,e,n){"use strict";var i=n("a159"),o=n("aebd"),r=n("45f2"),s={};n("35e8")(s,n("5168")("iterator"),function(){return this}),t.exports=function(t,e,n){t.prototype=i(s,{next:o(1,n)}),r(t,e+" Iterator")}},9003:function(t,e,n){var i=n("6b4c");t.exports=Array.isArray||function(t){return"Array"==i(t)}},9093:function(t,e,n){var i=n("ce10"),o=n("e11e2").concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return i(t,o)}},9138:function(t,e,n){t.exports=n("35e8")},9427:function(t,e,n){var i=n("63b6");i(i.S,"Object",{create:n("a159")})},"96cf":function(t,e,n){var i=function(t){"use strict";var e,n=Object.prototype,i=n.hasOwnProperty,o="function"===typeof Symbol?Symbol:{},r=o.iterator||"@@iterator",s=o.asyncIterator||"@@asyncIterator",a=o.toStringTag||"@@toStringTag";function u(t,e,n,i){var o=e&&e.prototype instanceof _?e:_,r=Object.create(o.prototype),s=new C(i||[]);return r._invoke=P(t,n,s),r}function h(t,e,n){try{return{type:"normal",arg:t.call(e,n)}}catch(i){return{type:"throw",arg:i}}}t.wrap=u;var c="suspendedStart",l="suspendedYield",f="executing",d="completed",p={};function _(){}function m(){}function v(){}var g={};g[r]=function(){return this};var y=Object.getPrototypeOf,b=y&&y(y(E([])));b&&b!==n&&i.call(b,r)&&(g=b);var x=v.prototype=_.prototype=Object.create(g);function w(t){["next","throw","return"].forEach(function(e){t[e]=function(t){return this._invoke(e,t)}})}function L(t){function e(n,o,r,s){var a=h(t[n],t,o);if("throw"!==a.type){var u=a.arg,c=u.value;return c&&"object"===typeof c&&i.call(c,"__await")?Promise.resolve(c.__await).then(function(t){e("next",t,r,s)},function(t){e("throw",t,r,s)}):Promise.resolve(c).then(function(t){u.value=t,r(u)},function(t){return e("throw",t,r,s)})}s(a.arg)}var n;function o(t,i){function o(){return new Promise(function(n,o){e(t,i,n,o)})}return n=n?n.then(o,o):o()}this._invoke=o}function P(t,e,n){var i=c;return function(o,r){if(i===f)throw new Error("Generator is already running");if(i===d){if("throw"===o)throw r;return O()}n.method=o,n.arg=r;while(1){var s=n.delegate;if(s){var a=T(s,n);if(a){if(a===p)continue;return a}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if(i===c)throw i=d,n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);i=f;var u=h(t,e,n);if("normal"===u.type){if(i=n.done?d:l,u.arg===p)continue;return{value:u.arg,done:n.done}}"throw"===u.type&&(i=d,n.method="throw",n.arg=u.arg)}}}function T(t,n){var i=t.iterator[n.method];if(i===e){if(n.delegate=null,"throw"===n.method){if(t.iterator["return"]&&(n.method="return",n.arg=e,T(t,n),"throw"===n.method))return p;n.method="throw",n.arg=new TypeError("The iterator does not provide a 'throw' method")}return p}var o=h(i,t.iterator,n.arg);if("throw"===o.type)return n.method="throw",n.arg=o.arg,n.delegate=null,p;var r=o.arg;return r?r.done?(n[t.resultName]=r.value,n.next=t.nextLoc,"return"!==n.method&&(n.method="next",n.arg=e),n.delegate=null,p):r:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,p)}function S(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function M(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function C(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(S,this),this.reset(!0)}function E(t){if(t){var n=t[r];if(n)return n.call(t);if("function"===typeof t.next)return t;if(!isNaN(t.length)){var o=-1,s=function n(){while(++o=0;--r){var s=this.tryEntries[r],a=s.completion;if("root"===s.tryLoc)return o("end");if(s.tryLoc<=this.prev){var u=i.call(s,"catchLoc"),h=i.call(s,"finallyLoc");if(u&&h){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&i.call(o,"finallyLoc")&&this.prev=0;--e){var n=this.tryEntries[e];if(n.finallyLoc===t)return this.complete(n.completion,n.afterLoc),M(n),p}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var n=this.tryEntries[e];if(n.tryLoc===t){var i=n.completion;if("throw"===i.type){var o=i.arg;M(n)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,n,i){return this.delegate={iterator:E(t),resultName:n,nextLoc:i},"next"===this.method&&(this.arg=e),p}},t}(t.exports);try{regeneratorRuntime=i}catch(o){Function("r","regeneratorRuntime = r")(i)}},"990b":function(t,e,n){var i=n("9093"),o=n("2621"),r=n("cb7c"),s=n("7726").Reflect;t.exports=s&&s.ownKeys||function(t){var e=i.f(r(t)),n=o.f;return n?e.concat(n(t)):e}},"9aa9":function(t,e){e.f=Object.getOwnPropertySymbols},"9b43":function(t,e,n){var i=n("d8e8");t.exports=function(t,e,n){if(i(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,i){return t.call(e,n,i)};case 3:return function(n,i,o){return t.call(e,n,i,o)}}return function(){return t.apply(e,arguments)}}},"9c6c":function(t,e,n){var i=n("2b4c")("unscopables"),o=Array.prototype;void 0==o[i]&&n("32e9")(o,i,{}),t.exports=function(t){o[i][t]=!0}},"9c80":function(t,e){t.exports=function(t){try{return{e:!1,v:t()}}catch(e){return{e:!0,v:e}}}},"9def":function(t,e,n){var i=n("4588"),o=Math.min;t.exports=function(t){return t>0?o(i(t),9007199254740991):0}},"9e1e":function(t,e,n){t.exports=!n("79e5")(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},a159:function(t,e,n){var i=n("e4ae"),o=n("7e90"),r=n("1691"),s=n("5559")("IE_PROTO"),a=function(){},u="prototype",h=function(){var t,e=n("1ec9")("iframe"),i=r.length,o="<",s=">";e.style.display="none",n("32fc").appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write(o+"script"+s+"document.F=Object"+o+"/script"+s),t.close(),h=t.F;while(i--)delete h[u][r[i]];return h()};t.exports=Object.create||function(t,e){var n;return null!==t?(a[u]=i(t),n=new a,a[u]=null,n[s]=t):n=h(),void 0===e?n:o(n,e)}},a22a:function(t,e,n){var i=n("d864"),o=n("b0dc"),r=n("3702"),s=n("e4ae"),a=n("b447"),u=n("7cd6"),h={},c={};e=t.exports=function(t,e,n,l,f){var d,p,_,m,v=f?function(){return t}:u(t),g=i(n,l,e?2:1),y=0;if("function"!=typeof v)throw TypeError(t+" is not iterable!");if(r(v)){for(d=a(t.length);d>y;y++)if(m=e?g(s(p=t[y])[0],p[1]):g(t[y]),m===h||m===c)return m}else for(_=v.call(t);!(p=_.next()).done;)if(m=o(_,g,p.value,e),m===h||m===c)return m};e.BREAK=h,e.RETURN=c},a25f:function(t,e,n){var i=n("7726"),o=i.navigator;t.exports=o&&o.userAgent||""},a481:function(t,e,n){"use strict";var i=n("cb7c"),o=n("4bf8"),r=n("9def"),s=n("4588"),a=n("0390"),u=n("5f1b"),h=Math.max,c=Math.min,l=Math.floor,f=/\$([$&`']|\d\d?|<[^>]*>)/g,d=/\$([$&`']|\d\d?)/g,p=function(t){return void 0===t?t:String(t)};n("214f")("replace",2,function(t,e,n,_){return[function(i,o){var r=t(this),s=void 0==i?void 0:i[e];return void 0!==s?s.call(i,r,o):n.call(String(r),i,o)},function(t,e){var o=_(n,t,this,e);if(o.done)return o.value;var l=i(t),f=String(this),d="function"===typeof e;d||(e=String(e));var v=l.global;if(v){var g=l.unicode;l.lastIndex=0}var y=[];while(1){var b=u(l,f);if(null===b)break;if(y.push(b),!v)break;var x=String(b[0]);""===x&&(l.lastIndex=a(f,r(l.lastIndex),g))}for(var w="",L=0,P=0;P=L&&(w+=f.slice(L,S)+z,L=S+T.length)}return w+f.slice(L)}];function m(t,e,i,r,s,a){var u=i+t.length,h=r.length,c=d;return void 0!==s&&(s=o(s),c=f),n.call(a,c,function(n,o){var a;switch(o.charAt(0)){case"$":return"$";case"&":return t;case"`":return e.slice(0,i);case"'":return e.slice(u);case"<":a=s[o.slice(1,-1)];break;default:var c=+o;if(0===c)return n;if(c>h){var f=l(c/10);return 0===f?n:f<=h?void 0===r[f-1]?o.charAt(1):r[f-1]+o.charAt(1):n}a=r[c-1]}return void 0===a?"":a})}})},a5b8:function(t,e,n){"use strict";var i=n("d8e8");function o(t){var e,n;this.promise=new t(function(t,i){if(void 0!==e||void 0!==n)throw TypeError("Bad Promise constructor");e=t,n=i}),this.resolve=i(e),this.reject=i(n)}t.exports.f=function(t){return new o(t)}},aa77:function(t,e,n){var i=n("5ca1"),o=n("be13"),r=n("79e5"),s=n("fdef"),a="["+s+"]",u="​…",h=RegExp("^"+a+a+"*"),c=RegExp(a+a+"*$"),l=function(t,e,n){var o={},a=r(function(){return!!s[t]()||u[t]()!=u}),h=o[t]=a?e(f):s[t];n&&(o[n]=h),i(i.P+i.F*a,"String",o)},f=l.trim=function(t,e){return t=String(o(t)),1&e&&(t=t.replace(h,"")),2&e&&(t=t.replace(c,"")),t};t.exports=l},aae3:function(t,e,n){var i=n("d3f4"),o=n("2d95"),r=n("2b4c")("match");t.exports=function(t){var e;return i(t)&&(void 0!==(e=t[r])?!!e:"RegExp"==o(t))}},aba2:function(t,e,n){var i=n("e53d"),o=n("4178").set,r=i.MutationObserver||i.WebKitMutationObserver,s=i.process,a=i.Promise,u="process"==n("6b4c")(s);t.exports=function(){var t,e,n,h=function(){var i,o;u&&(i=s.domain)&&i.exit();while(t){o=t.fn,t=t.next;try{o()}catch(r){throw t?n():e=void 0,r}}e=void 0,i&&i.enter()};if(u)n=function(){s.nextTick(h)};else if(!r||i.navigator&&i.navigator.standalone)if(a&&a.resolve){var c=a.resolve(void 0);n=function(){c.then(h)}}else n=function(){o.call(i,h)};else{var l=!0,f=document.createTextNode("");new r(h).observe(f,{characterData:!0}),n=function(){f.data=l=!l}}return function(i){var o={fn:i,next:void 0};e&&(e.next=o),t||(t=o,n()),e=o}}},ac6a:function(t,e,n){for(var i=n("cadf"),o=n("0d58"),r=n("2aba"),s=n("7726"),a=n("32e9"),u=n("84f2"),h=n("2b4c"),c=h("iterator"),l=h("toStringTag"),f=u.Array,d={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},p=o(d),_=0;_0?o(i(t),9007199254740991):0}},b8e3:function(t,e){t.exports=!0},bc13:function(t,e,n){var i=n("e53d"),o=i.navigator;t.exports=o&&o.userAgent||""},bcaa:function(t,e,n){var i=n("cb7c"),o=n("d3f4"),r=n("a5b8");t.exports=function(t,e){if(i(t),o(e)&&e.constructor===t)return e;var n=r.f(t),s=n.resolve;return s(e),n.promise}},bd86:function(t,e,n){"use strict";n.d(e,"a",function(){return r});var i=n("85f2"),o=n.n(i);function r(t,e,n){return e in t?o()(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}},be13:function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},bf0b:function(t,e,n){var i=n("355d"),o=n("aebd"),r=n("36c3"),s=n("1bc3"),a=n("07e3"),u=n("794b"),h=Object.getOwnPropertyDescriptor;e.f=n("8e60")?h:function(t,e){if(t=r(t),e=s(e,!0),u)try{return h(t,e)}catch(n){}if(a(t,e))return o(!i.f.call(t,e),t[e])}},c207:function(t,e){},c366:function(t,e,n){var i=n("6821"),o=n("9def"),r=n("77f1");t.exports=function(t){return function(e,n,s){var a,u=i(e),h=o(u.length),c=r(s,h);if(t&&n!=n){while(h>c)if(a=u[c++],a!=a)return!0}else for(;h>c;c++)if((t||c in u)&&u[c]===n)return t||c||0;return!t&&-1}}},c367:function(t,e,n){"use strict";var i=n("8436"),o=n("50ed"),r=n("481b"),s=n("36c3");t.exports=n("30f1")(Array,"Array",function(t,e){this._t=s(t),this._i=0,this._k=e},function(){var t=this._t,e=this._k,n=this._i++;return!t||n>=t.length?(this._t=void 0,o(1)):o(0,"keys"==e?n:"values"==e?t[n]:[n,t[n]])},"values"),r.Arguments=r.Array,i("keys"),i("values"),i("entries")},c3a1:function(t,e,n){var i=n("e6f3"),o=n("1691");t.exports=Object.keys||function(t){return i(t,o)}},c5f6:function(t,e,n){"use strict";var i=n("7726"),o=n("69a8"),r=n("2d95"),s=n("5dbc"),a=n("6a99"),u=n("79e5"),h=n("9093").f,c=n("11e9").f,l=n("86cc").f,f=n("aa77").trim,d="Number",p=i[d],_=p,m=p.prototype,v=r(n("2aeb")(m))==d,g="trim"in String.prototype,y=function(t){var e=a(t,!1);if("string"==typeof e&&e.length>2){e=g?e.trim():f(e,3);var n,i,o,r=e.charCodeAt(0);if(43===r||45===r){if(n=e.charCodeAt(2),88===n||120===n)return NaN}else if(48===r){switch(e.charCodeAt(1)){case 66:case 98:i=2,o=49;break;case 79:case 111:i=8,o=55;break;default:return+e}for(var s,u=e.slice(2),h=0,c=u.length;ho)return NaN;return parseInt(u,i)}}return+e};if(!p(" 0o1")||!p("0b1")||p("+0x1")){p=function(t){var e=arguments.length<1?0:t,n=this;return n instanceof p&&(v?u(function(){m.valueOf.call(n)}):r(n)!=d)?s(new _(y(e)),n,p):y(e)};for(var b,x=n("9e1e")?h(_):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),w=0;x.length>w;w++)o(_,b=x[w])&&!o(p,b)&&l(p,b,c(_,b));p.prototype=m,m.constructor=p,n("2aba")(i,d,p)}},c69a:function(t,e,n){t.exports=!n("9e1e")&&!n("79e5")(function(){return 7!=Object.defineProperty(n("230e")("div"),"a",{get:function(){return 7}}).a})},ca5a:function(t,e){var n=0,i=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+i).toString(36))}},cadf:function(t,e,n){"use strict";var i=n("9c6c"),o=n("d53b"),r=n("84f2"),s=n("6821");t.exports=n("01f9")(Array,"Array",function(t,e){this._t=s(t),this._i=0,this._k=e},function(){var t=this._t,e=this._k,n=this._i++;return!t||n>=t.length?(this._t=void 0,o(1)):o(0,"keys"==e?n:"values"==e?t[n]:[n,t[n]])},"values"),r.Arguments=r.Array,i("keys"),i("values"),i("entries")},cb7c:function(t,e,n){var i=n("d3f4");t.exports=function(t){if(!i(t))throw TypeError(t+" is not an object!");return t}},ccb9:function(t,e,n){e.f=n("5168")},cd78:function(t,e,n){var i=n("e4ae"),o=n("f772"),r=n("656e");t.exports=function(t,e){if(i(t),o(e)&&e.constructor===t)return e;var n=r.f(t),s=n.resolve;return s(e),n.promise}},ce10:function(t,e,n){var i=n("69a8"),o=n("6821"),r=n("c366")(!1),s=n("613b")("IE_PROTO");t.exports=function(t,e){var n,a=o(t),u=0,h=[];for(n in a)n!=s&&i(a,n)&&h.push(n);while(e.length>u)i(a,n=e[u++])&&(~r(h,n)||h.push(n));return h}},ce7e:function(t,e,n){var i=n("63b6"),o=n("584a"),r=n("294c");t.exports=function(t,e){var n=(o.Object||{})[t]||Object[t],s={};s[t]=e(n),i(i.S+i.F*r(function(){n(1)}),"Object",s)}},d225:function(t,e,n){"use strict";function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.d(e,"a",function(){return i})},d3f4:function(t,e){t.exports=function(t){return"object"===typeof t?null!==t:"function"===typeof t}},d53b:function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},d864:function(t,e,n){var i=n("79aa");t.exports=function(t,e,n){if(i(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,i){return t.call(e,n,i)};case 3:return function(n,i,o){return t.call(e,n,i,o)}}return function(){return t.apply(e,arguments)}}},d8d6:function(t,e,n){n("1654"),n("6c1c"),t.exports=n("ccb9").f("iterator")},d8e8:function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},d9f6:function(t,e,n){var i=n("e4ae"),o=n("794b"),r=n("1bc3"),s=Object.defineProperty;e.f=n("8e60")?Object.defineProperty:function(t,e,n){if(i(t),e=r(e,!0),i(n),o)try{return s(t,e,n)}catch(a){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},dbdb:function(t,e,n){var i=n("584a"),o=n("e53d"),r="__core-js_shared__",s=o[r]||(o[r]={});(t.exports=function(t,e){return s[t]||(s[t]=void 0!==e?e:{})})("versions",[]).push({version:i.version,mode:n("b8e3")?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},dc62:function(t,e,n){n("9427");var i=n("584a").Object;t.exports=function(t,e){return i.create(t,e)}},dcbc:function(t,e,n){var i=n("2aba");t.exports=function(t,e,n){for(var o in e)i(t,o,e[o],n);return t}},e11e:function(t,e,n){ /* @preserve * Leaflet 1.5.1+build.2e3e0ff, a JS library for interactive maps. http://leafletjs.com * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade */ (function(t,n){n(e)})(0,function(t){"use strict";var e="1.5.1+build.2e3e0ffb",n=Object.freeze;function i(t){var e,n,i,o;for(n=1,i=arguments.length;n0?Math.floor(t):Math.ceil(t)};function B(t,e,n){return t instanceof A?t:g(t)?new A(t[0],t[1]):void 0===t||null===t?t:"object"===typeof t&&"x"in t&&"y"in t?new A(t.x,t.y):new A(t,e,n)}function j(t,e){if(t)for(var n=e?[t,e]:t,i=0,o=n.length;i=this.min.x&&n.x<=this.max.x&&e.y>=this.min.y&&n.y<=this.max.y},intersects:function(t){t=R(t);var e=this.min,n=this.max,i=t.min,o=t.max,r=o.x>=e.x&&i.x<=n.x,s=o.y>=e.y&&i.y<=n.y;return r&&s},overlaps:function(t){t=R(t);var e=this.min,n=this.max,i=t.min,o=t.max,r=o.x>e.x&&i.xe.y&&i.y=i.lat&&n.lat<=o.lat&&e.lng>=i.lng&&n.lng<=o.lng},intersects:function(t){t=D(t);var e=this._southWest,n=this._northEast,i=t.getSouthWest(),o=t.getNorthEast(),r=o.lat>=e.lat&&i.lat<=n.lat,s=o.lng>=e.lng&&i.lng<=n.lng;return r&&s},overlaps:function(t){t=D(t);var e=this._southWest,n=this._northEast,i=t.getSouthWest(),o=t.getNorthEast(),r=o.lat>e.lat&&i.late.lng&&i.lng1,Ct=function(){return!!document.createElement("canvas").getContext}(),Et=!(!document.createElementNS||!X("svg").createSVGRect),Ot=!Et&&function(){try{var t=document.createElement("div");t.innerHTML='';var e=t.firstChild;return e.style.behavior="url(#default#VML)",e&&"object"===typeof e.adj}catch(n){return!1}}();function zt(t){return navigator.userAgent.toLowerCase().indexOf(t)>=0}var It=(Object.freeze||Object)({ie:tt,ielt9:et,edge:nt,webkit:it,android:ot,android23:rt,androidStock:at,opera:ut,chrome:ht,gecko:ct,safari:lt,phantom:ft,opera12:dt,win:pt,ie3d:_t,webkit3d:mt,gecko3d:vt,any3d:gt,mobile:yt,mobileWebkit:bt,mobileWebkit3d:xt,msPointer:wt,pointer:Lt,touch:Pt,mobileOpera:Tt,mobileGecko:St,retina:Mt,canvas:Ct,svg:Et,vml:Ot}),kt=wt?"MSPointerDown":"pointerdown",At=wt?"MSPointerMove":"pointermove",Zt=wt?"MSPointerUp":"pointerup",Bt=wt?"MSPointerCancel":"pointercancel",jt=["INPUT","SELECT","OPTION"],Rt={},Nt=!1,Dt=0;function Ft(t,e,n,i){return"touchstart"===e?Ht(t,n,i):"touchmove"===e?Kt(t,n,i):"touchend"===e&&$t(t,n,i),this}function Wt(t,e,n){var i=t["_leaflet_"+e+n];return"touchstart"===e?t.removeEventListener(kt,i,!1):"touchmove"===e?t.removeEventListener(At,i,!1):"touchend"===e&&(t.removeEventListener(Zt,i,!1),t.removeEventListener(Bt,i,!1)),this}function Ht(t,e,n){var i=r(function(t){if("mouse"!==t.pointerType&&t.MSPOINTER_TYPE_MOUSE&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE){if(!(jt.indexOf(t.target.tagName)<0))return;He(t)}qt(t,e)});t["_leaflet_touchstart"+n]=i,t.addEventListener(kt,i,!1),Nt||(document.documentElement.addEventListener(kt,Ut,!0),document.documentElement.addEventListener(At,Vt,!0),document.documentElement.addEventListener(Zt,Gt,!0),document.documentElement.addEventListener(Bt,Gt,!0),Nt=!0)}function Ut(t){Rt[t.pointerId]=t,Dt++}function Vt(t){Rt[t.pointerId]&&(Rt[t.pointerId]=t)}function Gt(t){delete Rt[t.pointerId],Dt--}function qt(t,e){for(var n in t.touches=[],Rt)t.touches.push(Rt[n]);t.changedTouches=[t],e(t)}function Kt(t,e,n){var i=function(t){(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons)&&qt(t,e)};t["_leaflet_touchmove"+n]=i,t.addEventListener(At,i,!1)}function $t(t,e,n){var i=function(t){qt(t,e)};t["_leaflet_touchend"+n]=i,t.addEventListener(Zt,i,!1),t.addEventListener(Bt,i,!1)}var Yt=wt?"MSPointerDown":Lt?"pointerdown":"touchstart",Xt=wt?"MSPointerUp":Lt?"pointerup":"touchend",Jt="_leaflet_";function Qt(t,e,n){var i,o,r=!1,s=250;function a(t){var e;if(Lt){if(!nt||"mouse"===t.pointerType)return;e=Dt}else e=t.touches.length;if(!(e>1)){var n=Date.now(),a=n-(i||n);o=t.touches?t.touches[0]:t,r=a>0&&a<=s,i=n}}function u(t){if(r&&!o.cancelBubble){if(Lt){if(!nt||"mouse"===t.pointerType)return;var n,s,a={};for(s in o)n=o[s],a[s]=n&&n.bind?n.bind(o):n;o=a}o.type="dblclick",o.button=0,e(o),i=null}}return t[Jt+Yt+n]=a,t[Jt+Xt+n]=u,t[Jt+"dblclick"+n]=e,t.addEventListener(Yt,a,!1),t.addEventListener(Xt,u,!1),t.addEventListener("dblclick",e,!1),this}function te(t,e){var n=t[Jt+Yt+e],i=t[Jt+Xt+e],o=t[Jt+"dblclick"+e];return t.removeEventListener(Yt,n,!1),t.removeEventListener(Xt,i,!1),nt||t.removeEventListener("dblclick",o,!1),this}var ee,ne,ie,oe,re,se=Le(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),ae=Le(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),ue="webkitTransition"===ae||"OTransition"===ae?ae+"End":"transitionend";function he(t){return"string"===typeof t?document.getElementById(t):t}function ce(t,e){var n=t.style[e]||t.currentStyle&&t.currentStyle[e];if((!n||"auto"===n)&&document.defaultView){var i=document.defaultView.getComputedStyle(t,null);n=i?i[e]:null}return"auto"===n?null:n}function le(t,e,n){var i=document.createElement(t);return i.className=e||"",n&&n.appendChild(i),i}function fe(t){var e=t.parentNode;e&&e.removeChild(t)}function de(t){while(t.firstChild)t.removeChild(t.firstChild)}function pe(t){var e=t.parentNode;e&&e.lastChild!==t&&e.appendChild(t)}function _e(t){var e=t.parentNode;e&&e.firstChild!==t&&e.insertBefore(t,e.firstChild)}function me(t,e){if(void 0!==t.classList)return t.classList.contains(e);var n=be(t);return n.length>0&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(n)}function ve(t,e){if(void 0!==t.classList)for(var n=d(e),i=0,o=n.length;i100&&i<500||t.target._simulatedClick&&!t._simulated?Ue(t):(Ke=n,e(t))}var tn=(Object.freeze||Object)({on:Ze,off:je,stopPropagation:De,disableScrollPropagation:Fe,disableClickPropagation:We,preventDefault:He,stop:Ue,getMousePosition:Ve,getWheelDelta:qe,fakeStop:Ye,skipped:Xe,isExternalTarget:Je,addListener:Ze,removeListener:je}),en=k.extend({run:function(t,e,n,i){this.stop(),this._el=t,this._inProgress=!0,this._duration=n||.25,this._easeOutPower=1/Math.max(i||.5,.2),this._startPos=Se(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=M(this._animate,this),this._step()},_step:function(t){var e=+new Date-this._startTime,n=1e3*this._duration;ethis.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,e){this._enforcingBounds=!0;var n=this.getCenter(),i=this._limitCenter(n,this._zoom,D(t));return n.equals(i)||this.panTo(i,e),this._enforcingBounds=!1,this},panInside:function(t,e){e=e||{};var n=B(e.paddingTopLeft||e.padding||[0,0]),i=B(e.paddingBottomRight||e.padding||[0,0]),o=this.getCenter(),r=this.project(o),s=this.project(t),a=this.getPixelBounds(),u=a.getSize().divideBy(2),h=R([a.min.add(n),a.max.subtract(i)]);if(!h.contains(s)){this._enforcingBounds=!0;var c=r.subtract(s),l=B(s.x+c.x,s.y+c.y);(s.xh.max.x)&&(l.x=r.x-c.x,c.x>0?l.x+=u.x-n.x:l.x-=u.x-i.x),(s.yh.max.y)&&(l.y=r.y-c.y,c.y>0?l.y+=u.y-n.y:l.y-=u.y-i.y),this.panTo(this.unproject(l),e),this._enforcingBounds=!1}return this},invalidateSize:function(t){if(!this._loaded)return this;t=i({animate:!1,pan:!0},!0===t?{animate:!0}:t);var e=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var n=this.getSize(),o=e.divideBy(2).round(),s=n.divideBy(2).round(),a=o.subtract(s);return a.x||a.y?(t.animate&&t.pan?this.panBy(a):(t.pan&&this._rawPanBy(a),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(r(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:e,newSize:n})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=i({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var e=r(this._handleGeolocationResponse,this),n=r(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,n,t):navigator.geolocation.getCurrentPosition(e,n,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e=t.code,n=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+n+"."})},_handleGeolocationResponse:function(t){var e=t.coords.latitude,n=t.coords.longitude,i=new F(e,n),o=i.toBounds(2*t.coords.accuracy),r=this._locateOptions;if(r.setView){var s=this.getBoundsZoom(o);this.setView(i,r.maxZoom?Math.min(s,r.maxZoom):s)}var a={latlng:i,bounds:o,timestamp:t.timestamp};for(var u in t.coords)"number"===typeof t.coords[u]&&(a[u]=t.coords[u]);this.fire("locationfound",a)},addHandler:function(t,e){if(!e)return this;var n=this[t]=new e(this);return this._handlers.push(n),this.options[t]&&n.enable(),this},remove:function(){if(this._initEvents(!0),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(e){this._container._leaflet_id=void 0,this._containerId=void 0}var t;for(t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),fe(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(C(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)fe(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,e){var n="leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),i=le("div",n,e||this._mapPane);return t&&(this._panes[t]=i),i},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds(),e=this.unproject(t.getBottomLeft()),n=this.unproject(t.getTopRight());return new N(e,n)},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,e,n){t=D(t),n=B(n||[0,0]);var i=this.getZoom()||0,o=this.getMinZoom(),r=this.getMaxZoom(),s=t.getNorthWest(),a=t.getSouthEast(),u=this.getSize().subtract(n),h=R(this.project(a,i),this.project(s,i)).getSize(),c=gt?this.options.zoomSnap:1,l=u.x/h.x,f=u.y/h.y,d=e?Math.max(l,f):Math.min(l,f);return i=this.getScaleZoom(d,i),c&&(i=Math.round(i/(c/100))*(c/100),i=e?Math.ceil(i/c)*c:Math.floor(i/c)*c),Math.max(o,Math.min(r,i))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new A(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,e){var n=this._getTopLeftPoint(t,e);return new j(n,n.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"===typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,e){var n=this.options.crs;return e=void 0===e?this._zoom:e,n.scale(t)/n.scale(e)},getScaleZoom:function(t,e){var n=this.options.crs;e=void 0===e?this._zoom:e;var i=n.zoom(t*n.scale(e));return isNaN(i)?1/0:i},project:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.latLngToPoint(W(t),e)},unproject:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.pointToLatLng(B(t),e)},layerPointToLatLng:function(t){var e=B(t).add(this.getPixelOrigin());return this.unproject(e)},latLngToLayerPoint:function(t){var e=this.project(W(t))._round();return e._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(W(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(D(t))},distance:function(t,e){return this.options.crs.distance(W(t),W(e))},containerPointToLayerPoint:function(t){return B(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return B(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var e=this.containerPointToLayerPoint(B(t));return this.layerPointToLatLng(e)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(W(t)))},mouseEventToContainerPoint:function(t){return Ve(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var e=this._container=he(t);if(!e)throw new Error("Map container not found.");if(e._leaflet_id)throw new Error("Map container is already initialized.");Ze(e,"scroll",this._onScroll,this),this._containerId=a(e)},_initLayout:function(){var t=this._container;this._fadeAnimated=this.options.fadeAnimation&>,ve(t,"leaflet-container"+(Pt?" leaflet-touch":"")+(Mt?" leaflet-retina":"")+(et?" leaflet-oldie":"")+(lt?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":""));var e=ce(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),Te(this._mapPane,new A(0,0)),this.createPane("tilePane"),this.createPane("shadowPane"),this.createPane("overlayPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(ve(t.markerPane,"leaflet-zoom-hide"),ve(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,e){Te(this._mapPane,new A(0,0));var n=!this._loaded;this._loaded=!0,e=this._limitZoom(e),this.fire("viewprereset");var i=this._zoom!==e;this._moveStart(i,!1)._move(t,e)._moveEnd(i),this.fire("viewreset"),n&&this.fire("load")},_moveStart:function(t,e){return t&&this.fire("zoomstart"),e||this.fire("movestart"),this},_move:function(t,e,n){void 0===e&&(e=this._zoom);var i=this._zoom!==e;return this._zoom=e,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),(i||n&&n.pinch)&&this.fire("zoom",n),this.fire("move",n)},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return C(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){Te(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={},this._targets[a(this._container)]=this;var e=t?je:Ze;e(this._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&e(window,"resize",this._onResize,this),gt&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){C(this._resizeRequest),this._resizeRequest=M(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,e){var n,i=[],o="mouseout"===e||"mouseover"===e,r=t.target||t.srcElement,s=!1;while(r){if(n=this._targets[a(r)],n&&("click"===e||"preclick"===e)&&!t._simulated&&this._draggableMoved(n)){s=!0;break}if(n&&n.listens(e,!0)){if(o&&!Je(r,t))break;if(i.push(n),o)break}if(r===this._container)break;r=r.parentNode}return i.length||s||o||!Je(r,t)||(i=[this]),i},_handleDOMEvent:function(t){if(this._loaded&&!Xe(t)){var e=t.type;"mousedown"!==e&&"keypress"!==e&&"keyup"!==e&&"keydown"!==e||Oe(t.target||t.srcElement),this._fireDOMEvent(t,e)}},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,e,n){if("click"===t.type){var o=i({},t);o.type="preclick",this._fireDOMEvent(o,o.type,n)}if(!t._stopped&&(n=(n||[]).concat(this._findEventTargets(t,e)),n.length)){var r=n[0];"contextmenu"===e&&r.listens(e,!0)&&He(t);var s={originalEvent:t};if("keypress"!==t.type&&"keydown"!==t.type&&"keyup"!==t.type){var a=r.getLatLng&&(!r._radius||r._radius<=10);s.containerPoint=a?this.latLngToContainerPoint(r.getLatLng()):this.mouseEventToContainerPoint(t),s.layerPoint=this.containerPointToLayerPoint(s.containerPoint),s.latlng=a?r.getLatLng():this.layerPointToLatLng(s.layerPoint)}for(var u=0;u0?Math.round(t-e)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(e))},_limitZoom:function(t){var e=this.getMinZoom(),n=this.getMaxZoom(),i=gt?this.options.zoomSnap:1;return i&&(t=Math.round(t/i)*i),Math.max(e,Math.min(n,t))},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){ge(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){var n=this._getCenterOffset(t)._trunc();return!(!0!==(e&&e.animate)&&!this.getSize().contains(n))&&(this.panBy(n,e),!0)},_createAnimProxy:function(){var t=this._proxy=le("div","leaflet-proxy leaflet-zoom-animated");this._panes.mapPane.appendChild(t),this.on("zoomanim",function(t){var e=se,n=this._proxy.style[e];Pe(this._proxy,this.project(t.center,t.zoom),this.getZoomScale(t.zoom,1)),n===this._proxy.style[e]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on("load moveend",function(){var t=this.getCenter(),e=this.getZoom();Pe(this._proxy,this.project(t,e),this.getZoomScale(e,1))},this),this._on("unload",this._destroyAnimProxy,this)},_destroyAnimProxy:function(){fe(this._proxy),delete this._proxy},_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,e,n){if(this._animatingZoom)return!0;if(n=n||{},!this._zoomAnimated||!1===n.animate||this._nothingToAnimate()||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var i=this.getZoomScale(e),o=this._getCenterOffset(t)._divideBy(1-1/i);return!(!0!==n.animate&&!this.getSize().contains(o))&&(M(function(){this._moveStart(!0,!1)._animateZoom(t,e,!0)},this),!0)},_animateZoom:function(t,e,n,i){this._mapPane&&(n&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=e,ve(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:e,noUpdate:i}),setTimeout(r(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&ge(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),M(function(){this._moveEnd(!0)},this))}});function on(t,e){return new nn(t,e)}var rn=O.extend({options:{position:"topright"},initialize:function(t){p(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var e=this._container=this.onAdd(t),n=this.getPosition(),i=t._controlCorners[n];return ve(e,"leaflet-control"),-1!==n.indexOf("bottom")?i.insertBefore(e,i.firstChild):i.appendChild(e),this._map.on("unload",this.remove,this),this},remove:function(){return this._map?(fe(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null,this):this},_refocusOnMap:function(t){this._map&&t&&t.screenX>0&&t.screenY>0&&this._map.getContainer().focus()}}),sn=function(t){return new rn(t)};nn.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){var t=this._controlCorners={},e="leaflet-",n=this._controlContainer=le("div",e+"control-container",this._container);function i(i,o){var r=e+i+" "+e+o;t[i+o]=le("div",r,n)}i("top","left"),i("top","right"),i("bottom","left"),i("bottom","right")},_clearControlPos:function(){for(var t in this._controlCorners)fe(this._controlCorners[t]);fe(this._controlContainer),delete this._controlCorners,delete this._controlContainer}});var an=rn.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,e,n,i){return n1,this._baseLayersList.style.display=t?"":"none"),this._separator.style.display=e&&t?"":"none",this},_onLayerChange:function(t){this._handlingClick||this._update();var e=this._getLayer(a(t.target)),n=e.overlay?"add"===t.type?"overlayadd":"overlayremove":"add"===t.type?"baselayerchange":null;n&&this._map.fire(n,e)},_createRadioElement:function(t,e){var n='",i=document.createElement("div");return i.innerHTML=n,i.firstChild},_addItem:function(t){var e,n=document.createElement("label"),i=this._map.hasLayer(t.layer);t.overlay?(e=document.createElement("input"),e.type="checkbox",e.className="leaflet-control-layers-selector",e.defaultChecked=i):e=this._createRadioElement("leaflet-base-layers_"+a(this),i),this._layerControlInputs.push(e),e.layerId=a(t.layer),Ze(e,"click",this._onInputClick,this);var o=document.createElement("span");o.innerHTML=" "+t.name;var r=document.createElement("div");n.appendChild(r),r.appendChild(e),r.appendChild(o);var s=t.overlay?this._overlaysList:this._baseLayersList;return s.appendChild(n),this._checkDisabledLayers(),n},_onInputClick:function(){var t,e,n=this._layerControlInputs,i=[],o=[];this._handlingClick=!0;for(var r=n.length-1;r>=0;r--)t=n[r],e=this._getLayer(t.layerId).layer,t.checked?i.push(e):t.checked||o.push(e);for(r=0;r=0;o--)t=n[o],e=this._getLayer(t.layerId).layer,t.disabled=void 0!==e.options.minZoom&&ie.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),un=function(t,e,n){return new an(t,e,n)},hn=rn.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"−",zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",n=le("div",e+" leaflet-bar"),i=this.options;return this._zoomInButton=this._createButton(i.zoomInText,i.zoomInTitle,e+"-in",n,this._zoomIn),this._zoomOutButton=this._createButton(i.zoomOutText,i.zoomOutTitle,e+"-out",n,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),n},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,e,n,i,o){var r=le("a",n,i);return r.innerHTML=t,r.href="#",r.title=e,r.setAttribute("role","button"),r.setAttribute("aria-label",e),We(r),Ze(r,"click",Ue),Ze(r,"click",o,this),Ze(r,"click",this._refocusOnMap,this),r},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";ge(this._zoomInButton,e),ge(this._zoomOutButton,e),(this._disabled||t._zoom===t.getMinZoom())&&ve(this._zoomOutButton,e),(this._disabled||t._zoom===t.getMaxZoom())&&ve(this._zoomInButton,e)}});nn.mergeOptions({zoomControl:!0}),nn.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new hn,this.addControl(this.zoomControl))});var cn=function(t){return new hn(t)},ln=rn.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var e="leaflet-control-scale",n=le("div",e),i=this.options;return this._addScales(i,e+"-line",n),t.on(i.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),n},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,n){t.metric&&(this._mScale=le("div",e,n)),t.imperial&&(this._iScale=le("div",e,n))},_update:function(){var t=this._map,e=t.getSize().y/2,n=t.distance(t.containerPointToLatLng([0,e]),t.containerPointToLatLng([this.options.maxWidth,e]));this._updateScales(n)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var e=this._getRoundNum(t),n=e<1e3?e+" m":e/1e3+" km";this._updateScale(this._mScale,n,e/t)},_updateImperial:function(t){var e,n,i,o=3.2808399*t;o>5280?(e=o/5280,n=this._getRoundNum(e),this._updateScale(this._iScale,n+" mi",n/e)):(i=this._getRoundNum(o),this._updateScale(this._iScale,i+" ft",i/o))},_updateScale:function(t,e,n){t.style.width=Math.round(this.options.maxWidth*n)+"px",t.innerHTML=e},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),n=t/e;return n=n>=10?10:n>=5?5:n>=3?3:n>=2?2:1,e*n}}),fn=function(t){return new ln(t)},dn=rn.extend({options:{position:"bottomright",prefix:'Leaflet'},initialize:function(t){p(this,t),this._attributions={}},onAdd:function(t){for(var e in t.attributionControl=this,this._container=le("div","leaflet-control-attribution"),We(this._container),t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):this},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):this},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions[e]&&t.push(e);var n=[];this.options.prefix&&n.push(this.options.prefix),t.length&&n.push(t.join(", ")),this._container.innerHTML=n.join(" | ")}}});nn.mergeOptions({attributionControl:!0}),nn.addInitHook(function(){this.options.attributionControl&&(new dn).addTo(this)});var pn=function(t){return new dn(t)};rn.Layers=an,rn.Zoom=hn,rn.Scale=ln,rn.Attribution=dn,sn.layers=un,sn.zoom=cn,sn.scale=fn,sn.attribution=pn;var _n=O.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled?this:(this._enabled=!0,this.addHooks(),this)},disable:function(){return this._enabled?(this._enabled=!1,this.removeHooks(),this):this},enabled:function(){return!!this._enabled}});_n.addTo=function(t,e){return t.addHandler(e,this),this};var mn,vn={Events:I},gn=Pt?"touchstart mousedown":"mousedown",yn={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},bn={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},xn=k.extend({options:{clickTolerance:3},initialize:function(t,e,n,i){p(this,i),this._element=t,this._dragStartTarget=e||t,this._preventOutline=n},enable:function(){this._enabled||(Ze(this._dragStartTarget,gn,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(xn._dragging===this&&this.finishDrag(),je(this._dragStartTarget,gn,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){if(!t._simulated&&this._enabled&&(this._moved=!1,!me(this._element,"leaflet-zoom-anim")&&!(xn._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches)&&(xn._dragging=this,this._preventOutline&&Oe(this._element),Ce(),ee(),!this._moving))){this.fire("down");var e=t.touches?t.touches[0]:t,n=Ie(this._element);this._startPoint=new A(e.clientX,e.clientY),this._parentScale=ke(n),Ze(document,bn[t.type],this._onMove,this),Ze(document,yn[t.type],this._onUp,this)}},_onMove:function(t){if(!t._simulated&&this._enabled)if(t.touches&&t.touches.length>1)this._moved=!0;else{var e=t.touches&&1===t.touches.length?t.touches[0]:t,n=new A(e.clientX,e.clientY)._subtract(this._startPoint);(n.x||n.y)&&(Math.abs(n.x)+Math.abs(n.y)u&&(r=s,u=a);u>n&&(e[r]=1,Sn(t,e,n,i,r),Sn(t,e,n,r,o))}function Mn(t,e){for(var n=[t[0]],i=1,o=0,r=t.length;ie&&(n.push(t[i]),o=i);return oe.max.x&&(n|=2),t.ye.max.y&&(n|=8),n}function zn(t,e){var n=e.x-t.x,i=e.y-t.y;return n*n+i*i}function In(t,e,n,i){var o,r=e.x,s=e.y,a=n.x-r,u=n.y-s,h=a*a+u*u;return h>0&&(o=((t.x-r)*a+(t.y-s)*u)/h,o>1?(r=n.x,s=n.y):o>0&&(r+=a*o,s+=u*o)),a=t.x-r,u=t.y-s,i?a*a+u*u:new A(r,s)}function kn(t){return!g(t[0])||"object"!==typeof t[0][0]&&"undefined"!==typeof t[0][0]}function An(t){return console.warn("Deprecated use of _flat, please use L.LineUtil.isFlat instead."),kn(t)}var Zn=(Object.freeze||Object)({simplify:wn,pointToSegmentDistance:Ln,closestPointOnSegment:Pn,clipSegment:Cn,_getEdgeIntersection:En,_getBitCode:On,_sqClosestPointOnSegment:In,isFlat:kn,_flat:An});function Bn(t,e,n){var i,o,r,s,a,u,h,c,l,f=[1,4,2,8];for(o=0,h=t.length;o1e-7;u++)e=r*Math.sin(a),e=Math.pow((1-e)/(1+e),r/2),h=Math.PI/2-2*Math.atan(s*e)-a,a+=h;return new F(a*n,t.x*n/i)}},Dn=(Object.freeze||Object)({LonLat:Rn,Mercator:Nn,SphericalMercator:G}),Fn=i({},U,{code:"EPSG:3395",projection:Nn,transformation:function(){var t=.5/(Math.PI*Nn.R);return K(t,.5,-t,.5)}()}),Wn=i({},U,{code:"EPSG:4326",projection:Rn,transformation:K(1/180,1,-1/180,.5)}),Hn=i({},H,{projection:Rn,transformation:K(1,0,-1,0),scale:function(t){return Math.pow(2,t)},zoom:function(t){return Math.log(t)/Math.LN2},distance:function(t,e){var n=e.lng-t.lng,i=e.lat-t.lat;return Math.sqrt(n*n+i*i)},infinite:!0});H.Earth=U,H.EPSG3395=Fn,H.EPSG3857=$,H.EPSG900913=Y,H.EPSG4326=Wn,H.Simple=Hn;var Un=k.extend({options:{pane:"overlayPane",attribution:null,bubblingMouseEvents:!0},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[a(t)]=this,this},removeInteractiveTarget:function(t){return delete this._map._targets[a(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var e=t.target;if(e.hasLayer(this)){if(this._map=e,this._zoomAnimated=e._zoomAnimated,this.getEvents){var n=this.getEvents();e.on(n,this),this.once("remove",function(){e.off(n,this)},this)}this.onAdd(e),this.getAttribution&&e.attributionControl&&e.attributionControl.addAttribution(this.getAttribution()),this.fire("add"),e.fire("layeradd",{layer:this})}}});nn.include({addLayer:function(t){if(!t._layerAdd)throw new Error("The provided object is not a Layer.");var e=a(t);return this._layers[e]?this:(this._layers[e]=t,t._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t),this)},removeLayer:function(t){var e=a(t);return this._layers[e]?(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[e],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null,this):this},hasLayer:function(t){return!!t&&a(t)in this._layers},eachLayer:function(t,e){for(var n in this._layers)t.call(e,this._layers[n]);return this},_addLayers:function(t){t=t?g(t)?t:[t]:[];for(var e=0,n=t.length;ethis._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()e)return s=(i-e)/n,this._map.layerPointToLatLng([r.x-s*(r.x-o.x),r.y-s*(r.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,e){return e=e||this._defaultShape(),t=W(t),e.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new N,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return kn(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var e=[],n=kn(t),i=0,o=t.length;i=2&&e[0]instanceof F&&e[0].equals(e[n-1])&&e.pop(),e},_setLatLngs:function(t){si.prototype._setLatLngs.call(this,t),kn(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return kn(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,e=this.options.weight,n=new A(e,e);if(t=new j(t.min.subtract(n),t.max.add(n)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var i,o=0,r=this._rings.length;ot.y!==i.y>t.y&&t.x<(i.x-n.x)*(t.y-n.y)/(i.y-n.y)+n.x&&(h=!h);return h||si.prototype._containsPoint.call(this,t,!0)}});function hi(t,e){return new ui(t,e)}var ci=qn.extend({initialize:function(t,e){p(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,n,i,o=g(t)?t:t.features;if(o){for(e=0,n=o.length;e0?i:[e.src]}else{g(this._url)||(this._url=[this._url]),!this.options.keepAspectRatio&&e.style.hasOwnProperty("objectFit")&&(e.style["objectFit"]="fill"),e.autoplay=!!this.options.autoplay,e.loop=!!this.options.loop;for(var s=0;so?(e.height=o+"px",ve(t,r)):ge(t,r),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),n=this._getAnchor();Te(this._container,e.add(n))},_adjustPan:function(){if(this.options.autoPan){this._map._panAnim&&this._map._panAnim.stop();var t=this._map,e=parseInt(ce(this._container,"marginBottom"),10)||0,n=this._container.offsetHeight+e,i=this._containerWidth,o=new A(this._containerLeft,-n-this._containerBottom);o._add(Se(this._container));var r=t.layerPointToContainerPoint(o),s=B(this.options.autoPanPadding),a=B(this.options.autoPanPaddingTopLeft||s),u=B(this.options.autoPanPaddingBottomRight||s),h=t.getSize(),c=0,l=0;r.x+i+u.x>h.x&&(c=r.x+i-h.x+u.x),r.x-c-a.x<0&&(c=r.x-a.x),r.y+n+u.y>h.y&&(l=r.y+n-h.y+u.y),r.y-l-a.y<0&&(l=r.y-a.y),(c||l)&&t.fire("autopanstart").panBy([c,l])}},_onCloseButtonClick:function(t){this._close(),Ue(t)},_getAnchor:function(){return B(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}}),Ei=function(t,e){return new Ci(t,e)};nn.mergeOptions({closePopupOnClick:!0}),nn.include({openPopup:function(t,e,n){return t instanceof Ci||(t=new Ci(n).setContent(t)),e&&t.setLatLng(e),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),Un.include({bindPopup:function(t,e){return t instanceof Ci?(p(t,e),this._popup=t,t._source=this):(this._popup&&!e||(this._popup=new Ci(e,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,e){return this._popup&&this._map&&(e=this._popup._prepareOpen(this,t,e),this._map.openPopup(this._popup,e)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var e=t.layer||t.target;this._popup&&this._map&&(Ue(t),e instanceof ei?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===e?this.closePopup():this.openPopup(e,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var Oi=Mi.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){Mi.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){Mi.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=Mi.prototype.getEvents.call(this);return Pt&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip",e=t+" "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=le("div",e)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var e=this._map,n=this._container,i=e.latLngToContainerPoint(e.getCenter()),o=e.layerPointToContainerPoint(t),r=this.options.direction,s=n.offsetWidth,a=n.offsetHeight,u=B(this.options.offset),h=this._getAnchor();"top"===r?t=t.add(B(-s/2+u.x,-a+u.y+h.y,!0)):"bottom"===r?t=t.subtract(B(s/2-u.x,-u.y,!0)):"center"===r?t=t.subtract(B(s/2+u.x,a/2-h.y+u.y,!0)):"right"===r||"auto"===r&&o.xthis.options.maxZoom||ni&&this._retainParent(o,r,s,i))},_retainChildren:function(t,e,n,i){for(var o=2*t;o<2*t+2;o++)for(var r=2*e;r<2*e+2;r++){var s=new A(o,r);s.z=n+1;var a=this._tileCoordsToKey(s),u=this._tiles[a];u&&u.active?u.retain=!0:(u&&u.loaded&&(u.retain=!0),n+1this.options.maxZoom||void 0!==this.options.minZoom&&o1)this._setView(t,n);else{for(var l=o.min.y;l<=o.max.y;l++)for(var f=o.min.x;f<=o.max.x;f++){var d=new A(f,l);if(d.z=this._tileZoom,this._isValidTile(d)){var p=this._tiles[this._tileCoordsToKey(d)];p?p.current=!0:s.push(d)}}if(s.sort(function(t,e){return t.distanceTo(r)-e.distanceTo(r)}),0!==s.length){this._loading||(this._loading=!0,this.fire("loading"));var _=document.createDocumentFragment();for(f=0;fn.max.x)||!e.wrapLat&&(t.yn.max.y))return!1}if(!this.options.bounds)return!0;var i=this._tileCoordsToBounds(t);return D(this.options.bounds).overlaps(i)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var e=this._map,n=this.getTileSize(),i=t.scaleBy(n),o=i.add(n),r=e.unproject(i,t.z),s=e.unproject(o,t.z);return[r,s]},_tileCoordsToBounds:function(t){var e=this._tileCoordsToNwSe(t),n=new N(e[0],e[1]);return this.options.noWrap||(n=this._map.wrapLatLngBounds(n)),n},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var e=t.split(":"),n=new A(+e[0],+e[1]);return n.z=+e[2],n},_removeTile:function(t){var e=this._tiles[t];e&&(fe(e.el),delete this._tiles[t],this.fire("tileunload",{tile:e.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){ve(t,"leaflet-tile");var e=this.getTileSize();t.style.width=e.x+"px",t.style.height=e.y+"px",t.onselectstart=c,t.onmousemove=c,et&&this.options.opacity<1&&xe(t,this.options.opacity),ot&&!rt&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,e){var n=this._getTilePos(t),i=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),r(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&M(r(this._tileReady,this,t,null,o)),Te(o,n),this._tiles[i]={el:o,coords:t,current:!0},e.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,e,n){e&&this.fire("tileerror",{error:e,tile:n,coords:t});var i=this._tileCoordsToKey(t);n=this._tiles[i],n&&(n.loaded=+new Date,this._map._fadeAnimated?(xe(n.el,0),C(this._fadeFrame),this._fadeFrame=M(this._updateOpacity,this)):(n.active=!0,this._pruneTiles()),e||(ve(n.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:n.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),et||!this._map._fadeAnimated?M(this._pruneTiles,this):setTimeout(r(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var e=new A(this._wrapX?h(t.x,this._wrapX):t.x,this._wrapY?h(t.y,this._wrapY):t.y);return e.z=t.z,e},_pxBoundsToTileRange:function(t){var e=this.getTileSize();return new j(t.min.unscaleBy(e).floor(),t.max.unscaleBy(e).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});function Zi(t){return new Ai(t)}var Bi=Ai.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,e){this._url=t,e=p(this,e),e.detectRetina&&Mt&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomReverse?(e.zoomOffset--,e.minZoom++):(e.zoomOffset++,e.maxZoom--),e.minZoom=Math.max(0,e.minZoom)),"string"===typeof e.subdomains&&(e.subdomains=e.subdomains.split("")),ot||this.on("tileunload",this._onTileRemove)},setUrl:function(t,e){return this._url===t&&void 0===e&&(e=!0),this._url=t,e||this.redraw(),this},createTile:function(t,e){var n=document.createElement("img");return Ze(n,"load",r(this._tileOnLoad,this,e,n)),Ze(n,"error",r(this._tileOnError,this,e,n)),(this.options.crossOrigin||""===this.options.crossOrigin)&&(n.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),n.alt="",n.setAttribute("role","presentation"),n.src=this.getTileUrl(t),n},getTileUrl:function(t){var e={r:Mt?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var n=this._globalTileRange.max.y-t.y;this.options.tms&&(e["y"]=n),e["-y"]=n}return v(this._url,i(e,this.options))},_tileOnLoad:function(t,e){et?setTimeout(r(t,this,null,e),0):t(null,e)},_tileOnError:function(t,e,n){var i=this.options.errorTileUrl;i&&e.getAttribute("src")!==i&&(e.src=i),t(n,e)},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,e=this.options.maxZoom,n=this.options.zoomReverse,i=this.options.zoomOffset;return n&&(t=e-t),t+i},_getSubdomain:function(t){var e=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_abortLoading:function(){var t,e;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&(e=this._tiles[t].el,e.onload=c,e.onerror=c,e.complete||(e.src=b,fe(e),delete this._tiles[t]))},_removeTile:function(t){var e=this._tiles[t];if(e)return at||e.el.setAttribute("src",b),Ai.prototype._removeTile.call(this,t)},_tileReady:function(t,e,n){if(this._map&&(!n||n.getAttribute("src")!==b))return Ai.prototype._tileReady.call(this,t,e,n)}});function ji(t,e){return new Bi(t,e)}var Ri=Bi.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,e){this._url=t;var n=i({},this.defaultWmsParams);for(var o in e)o in this.options||(n[o]=e[o]);e=p(this,e);var r=e.detectRetina&&Mt?2:1,s=this.getTileSize();n.width=s.x*r,n.height=s.y*r,this.wmsParams=n},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var e=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[e]=this._crs.code,Bi.prototype.onAdd.call(this,t)},getTileUrl:function(t){var e=this._tileCoordsToNwSe(t),n=this._crs,i=R(n.project(e[0]),n.project(e[1])),o=i.min,r=i.max,s=(this._wmsVersion>=1.3&&this._crs===Wn?[o.y,o.x,r.y,r.x]:[o.x,o.y,r.x,r.y]).join(","),a=Bi.prototype.getTileUrl.call(this,t);return a+_(this.wmsParams,a,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+s},setParams:function(t,e){return i(this.wmsParams,t),e||this.redraw(),this}});function Ni(t,e){return new Ri(t,e)}Bi.WMS=Ri,ji.wms=Ni;var Di=Un.extend({options:{padding:.1,tolerance:0},initialize:function(t){p(this,t),a(this),this._layers=this._layers||{}},onAdd:function(){this._container||(this._initContainer(),this._zoomAnimated&&ve(this._container,"leaflet-zoom-animated")),this.getPane().appendChild(this._container),this._update(),this.on("update",this._updatePaths,this)},onRemove:function(){this.off("update",this._updatePaths,this),this._destroyContainer()},getEvents:function(){var t={viewreset:this._reset,zoom:this._onZoom,moveend:this._update,zoomend:this._onZoomEnd};return this._zoomAnimated&&(t.zoomanim=this._onAnimZoom),t},_onAnimZoom:function(t){this._updateTransform(t.center,t.zoom)},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_updateTransform:function(t,e){var n=this._map.getZoomScale(e,this._zoom),i=Se(this._container),o=this._map.getSize().multiplyBy(.5+this.options.padding),r=this._map.project(this._center,e),s=this._map.project(t,e),a=s.subtract(r),u=o.multiplyBy(-n).add(i).add(o).subtract(a);gt?Pe(this._container,u,n):Te(this._container,u)},_reset:function(){for(var t in this._update(),this._updateTransform(this._center,this._zoom),this._layers)this._layers[t]._reset()},_onZoomEnd:function(){for(var t in this._layers)this._layers[t]._project()},_updatePaths:function(){for(var t in this._layers)this._layers[t]._update()},_update:function(){var t=this.options.padding,e=this._map.getSize(),n=this._map.containerPointToLayerPoint(e.multiplyBy(-t)).round();this._bounds=new j(n,n.add(e.multiplyBy(1+2*t)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom()}}),Fi=Di.extend({getEvents:function(){var t=Di.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){Di.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=document.createElement("canvas");Ze(t,"mousemove",u(this._onMouseMove,32,this),this),Ze(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this),Ze(t,"mouseout",this._handleMouseOut,this),this._ctx=t.getContext("2d")},_destroyContainer:function(){C(this._redrawRequest),delete this._ctx,fe(this._container),je(this._container),delete this._container},_updatePaths:function(){if(!this._postponeUpdatePaths){var t;for(var e in this._redrawBounds=null,this._layers)t=this._layers[e],t._update();this._redraw()}},_update:function(){if(!this._map._animatingZoom||!this._bounds){Di.prototype._update.call(this);var t=this._bounds,e=this._container,n=t.getSize(),i=Mt?2:1;Te(e,t.min),e.width=i*n.x,e.height=i*n.y,e.style.width=n.x+"px",e.style.height=n.y+"px",Mt&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update")}},_reset:function(){Di.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t),this._layers[a(t)]=t;var e=t._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=e),this._drawLast=e,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var e=t._order,n=e.next,i=e.prev;n?n.prev=i:this._drawLast=i,i?i.next=n:this._drawFirst=n,delete t._order,delete this._layers[a(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if("string"===typeof t.options.dashArray){var e,n,i=t.options.dashArray.split(/[, ]+/),o=[];for(n=0;n')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),Ui={_initContainer:function(){this._container=le("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(Di.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var e=t._container=Hi("shape");ve(e,"leaflet-vml-shape "+(this.options.className||"")),e.coordsize="1 1",t._path=Hi("path"),e.appendChild(t._path),this._updateStyle(t),this._layers[a(t)]=t},_addPath:function(t){var e=t._container;this._container.appendChild(e),t.options.interactive&&t.addInteractiveTarget(e)},_removePath:function(t){var e=t._container;fe(e),t.removeInteractiveTarget(e),delete this._layers[a(t)]},_updateStyle:function(t){var e=t._stroke,n=t._fill,i=t.options,o=t._container;o.stroked=!!i.stroke,o.filled=!!i.fill,i.stroke?(e||(e=t._stroke=Hi("stroke")),o.appendChild(e),e.weight=i.weight+"px",e.color=i.color,e.opacity=i.opacity,i.dashArray?e.dashStyle=g(i.dashArray)?i.dashArray.join(" "):i.dashArray.replace(/( *, *)/g," "):e.dashStyle="",e.endcap=i.lineCap.replace("butt","flat"),e.joinstyle=i.lineJoin):e&&(o.removeChild(e),t._stroke=null),i.fill?(n||(n=t._fill=Hi("fill")),o.appendChild(n),n.color=i.fillColor||i.color,n.opacity=i.fillOpacity):n&&(o.removeChild(n),t._fill=null)},_updateCircle:function(t){var e=t._point.round(),n=Math.round(t._radius),i=Math.round(t._radiusY||n);this._setPath(t,t._empty()?"M0 0":"AL "+e.x+","+e.y+" "+n+","+i+" 0,23592600")},_setPath:function(t,e){t._path.v=e},_bringToFront:function(t){pe(t._container)},_bringToBack:function(t){_e(t._container)}},Vi=Ot?Hi:X,Gi=Di.extend({getEvents:function(){var t=Di.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=Vi("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=Vi("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){fe(this._container),je(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){if(!this._map._animatingZoom||!this._bounds){Di.prototype._update.call(this);var t=this._bounds,e=t.getSize(),n=this._container;this._svgSize&&this._svgSize.equals(e)||(this._svgSize=e,n.setAttribute("width",e.x),n.setAttribute("height",e.y)),Te(n,t.min),n.setAttribute("viewBox",[t.min.x,t.min.y,e.x,e.y].join(" ")),this.fire("update")}},_initPath:function(t){var e=t._path=Vi("path");t.options.className&&ve(e,t.options.className),t.options.interactive&&ve(e,"leaflet-interactive"),this._updateStyle(t),this._layers[a(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){fe(t._path),t.removeInteractiveTarget(t._path),delete this._layers[a(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var e=t._path,n=t.options;e&&(n.stroke?(e.setAttribute("stroke",n.color),e.setAttribute("stroke-opacity",n.opacity),e.setAttribute("stroke-width",n.weight),e.setAttribute("stroke-linecap",n.lineCap),e.setAttribute("stroke-linejoin",n.lineJoin),n.dashArray?e.setAttribute("stroke-dasharray",n.dashArray):e.removeAttribute("stroke-dasharray"),n.dashOffset?e.setAttribute("stroke-dashoffset",n.dashOffset):e.removeAttribute("stroke-dashoffset")):e.setAttribute("stroke","none"),n.fill?(e.setAttribute("fill",n.fillColor||n.color),e.setAttribute("fill-opacity",n.fillOpacity),e.setAttribute("fill-rule",n.fillRule||"evenodd")):e.setAttribute("fill","none"))},_updatePoly:function(t,e){this._setPath(t,J(t._parts,e))},_updateCircle:function(t){var e=t._point,n=Math.max(Math.round(t._radius),1),i=Math.max(Math.round(t._radiusY),1)||n,o="a"+n+","+i+" 0 1,0 ",r=t._empty()?"M0 0":"M"+(e.x-n)+","+e.y+o+2*n+",0 "+o+2*-n+",0 ";this._setPath(t,r)},_setPath:function(t,e){t._path.setAttribute("d",e)},_bringToFront:function(t){pe(t._path)},_bringToBack:function(t){_e(t._path)}});function qi(t){return Et||Ot?new Gi(t):null}Ot&&Gi.include(Ui),nn.include({getRenderer:function(t){var e=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer;return e||(e=this._renderer=this._createRenderer()),this.hasLayer(e)||this.addLayer(e),e},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var e=this._paneRenderers[t];return void 0===e&&(e=this._createRenderer({pane:t}),this._paneRenderers[t]=e),e},_createRenderer:function(t){return this.options.preferCanvas&&Wi(t)||qi(t)}});var Ki=ui.extend({initialize:function(t,e){ui.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=D(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});function $i(t,e){return new Ki(t,e)}Gi.create=Vi,Gi.pointsToPath=J,ci.geometryToLayer=li,ci.coordsToLatLng=fi,ci.coordsToLatLngs=di,ci.latLngToCoords=pi,ci.latLngsToCoords=_i,ci.getFeature=mi,ci.asFeature=vi,nn.mergeOptions({boxZoom:!0});var Yi=_n.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){Ze(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){je(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){fe(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),ee(),Ce(),this._startPoint=this._map.mouseEventToContainerPoint(t),Ze(document,{contextmenu:Ue,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=le("div","leaflet-zoom-box",this._container),ve(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var e=new j(this._point,this._startPoint),n=e.getSize();Te(this._box,e.min),this._box.style.width=n.x+"px",this._box.style.height=n.y+"px"},_finish:function(){this._moved&&(fe(this._box),ge(this._container,"leaflet-crosshair")),ne(),Ee(),je(document,{contextmenu:Ue,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(r(this._resetState,this),0);var e=new N(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(e).fire("boxzoomend",{boxZoomBounds:e})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});nn.addInitHook("addHandler","boxZoom",Yi),nn.mergeOptions({doubleClickZoom:!0});var Xi=_n.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var e=this._map,n=e.getZoom(),i=e.options.zoomDelta,o=t.originalEvent.shiftKey?n-i:n+i;"center"===e.options.doubleClickZoom?e.setZoom(o):e.setZoomAround(t.containerPoint,o)}});nn.addInitHook("addHandler","doubleClickZoom",Xi),nn.mergeOptions({dragging:!0,inertia:!rt,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var Ji=_n.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new xn(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))}ve(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){ge(this._map._container,"leaflet-grab"),ge(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t=this._map;if(t._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity){var e=D(this._map.options.maxBounds);this._offsetLimit=R(this._map.latLngToContainerPoint(e.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(e.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))}else this._offsetLimit=null;t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){if(this._map.options.inertia){var e=this._lastTime=+new Date,n=this._lastPos=this._draggable._absPos||this._draggable._newPos;this._positions.push(n),this._times.push(e),this._prunePositions(e)}this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){while(this._positions.length>1&&t-this._times[0]>50)this._positions.shift(),this._times.shift()},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),e=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,e){return t-(t-e)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),e=this._offsetLimit;t.xe.max.x&&(t.x=this._viscousLimit(t.x,e.max.x)),t.y>e.max.y&&(t.y=this._viscousLimit(t.y,e.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,e=Math.round(t/2),n=this._initialWorldOffset,i=this._draggable._newPos.x,o=(i-e+n)%t+e-n,r=(i+e+n)%t-e-n,s=Math.abs(o+n)0?r:-r))-e;this._delta=0,this._startTime=null,s&&("center"===t.options.scrollWheelZoom?t.setZoom(e+s):t.setZoomAround(this._lastMousePos,e+s))}});nn.addInitHook("addHandler","scrollWheelZoom",to),nn.mergeOptions({tap:!0,tapTolerance:15});var eo=_n.extend({addHooks:function(){Ze(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){je(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(He(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var e=t.touches[0],n=e.target;this._startPos=this._newPos=new A(e.clientX,e.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&ve(n,"leaflet-active"),this._holdTimeout=setTimeout(r(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",e))},this),1e3),this._simulateEvent("mousedown",e),Ze(document,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),je(document,{touchmove:this._onMove,touchend:this._onUp},this),this._fireClick&&t&&t.changedTouches){var e=t.changedTouches[0],n=e.target;n&&n.tagName&&"a"===n.tagName.toLowerCase()&&ge(n,"leaflet-active"),this._simulateEvent("mouseup",e),this._isTapValid()&&this._simulateEvent("click",e)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var e=t.touches[0];this._newPos=new A(e.clientX,e.clientY),this._simulateEvent("mousemove",e)},_simulateEvent:function(t,e){var n=document.createEvent("MouseEvents");n._simulated=!0,e.target._simulatedClick=!0,n.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,!1,!1,!1,!1,0,null),e.target.dispatchEvent(n)}});Pt&&!Lt&&nn.addInitHook("addHandler","tap",eo),nn.mergeOptions({touchZoom:Pt&&!rt,bounceAtZoomLimits:!0});var no=_n.extend({addHooks:function(){ve(this._map._container,"leaflet-touch-zoom"),Ze(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){ge(this._map._container,"leaflet-touch-zoom"),je(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var e=this._map;if(t.touches&&2===t.touches.length&&!e._animatingZoom&&!this._zooming){var n=e.mouseEventToContainerPoint(t.touches[0]),i=e.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=e.getSize()._divideBy(2),this._startLatLng=e.containerPointToLatLng(this._centerPoint),"center"!==e.options.touchZoom&&(this._pinchStartLatLng=e.containerPointToLatLng(n.add(i)._divideBy(2))),this._startDist=n.distanceTo(i),this._startZoom=e.getZoom(),this._moved=!1,this._zooming=!0,e._stop(),Ze(document,"touchmove",this._onTouchMove,this),Ze(document,"touchend",this._onTouchEnd,this),He(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var e=this._map,n=e.mouseEventToContainerPoint(t.touches[0]),i=e.mouseEventToContainerPoint(t.touches[1]),o=n.distanceTo(i)/this._startDist;if(this._zoom=e.getScaleZoom(o,this._startZoom),!e.options.bounceAtZoomLimits&&(this._zoome.getMaxZoom()&&o>1)&&(this._zoom=e._limitZoom(this._zoom)),"center"===e.options.touchZoom){if(this._center=this._startLatLng,1===o)return}else{var s=n._add(i)._divideBy(2)._subtract(this._centerPoint);if(1===o&&0===s.x&&0===s.y)return;this._center=e.unproject(e.project(this._pinchStartLatLng,this._zoom).subtract(s),this._zoom)}this._moved||(e._moveStart(!0,!1),this._moved=!0),C(this._animRequest);var a=r(e._move,e,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=M(a,this,!0),He(t)}},_onTouchEnd:function(){this._moved&&this._zooming?(this._zooming=!1,C(this._animRequest),je(document,"touchmove",this._onTouchMove),je(document,"touchend",this._onTouchEnd),this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom))):this._zooming=!1}});nn.addInitHook("addHandler","touchZoom",no),nn.BoxZoom=Yi,nn.DoubleClickZoom=Xi,nn.Drag=Ji,nn.Keyboard=Qi,nn.ScrollWheelZoom=to,nn.Tap=eo,nn.TouchZoom=no,Object.freeze=n,t.version=e,t.Control=rn,t.control=sn,t.Browser=It,t.Evented=k,t.Mixin=vn,t.Util=E,t.Class=O,t.Handler=_n,t.extend=i,t.bind=r,t.stamp=a,t.setOptions=p,t.DomEvent=tn,t.DomUtil=Ae,t.PosAnimation=en,t.Draggable=xn,t.LineUtil=Zn,t.PolyUtil=jn,t.Point=A,t.point=B,t.Bounds=j,t.bounds=R,t.Transformation=q,t.transformation=K,t.Projection=Dn,t.LatLng=F,t.latLng=W,t.LatLngBounds=N,t.latLngBounds=D,t.CRS=H,t.GeoJSON=ci,t.geoJSON=yi,t.geoJson=bi,t.Layer=Un,t.LayerGroup=Vn,t.layerGroup=Gn,t.FeatureGroup=qn,t.featureGroup=Kn,t.ImageOverlay=xi,t.imageOverlay=wi,t.VideoOverlay=Li,t.videoOverlay=Pi,t.SVGOverlay=Ti,t.svgOverlay=Si,t.DivOverlay=Mi,t.Popup=Ci,t.popup=Ei,t.Tooltip=Oi,t.tooltip=zi,t.Icon=$n,t.icon=Yn,t.DivIcon=Ii,t.divIcon=ki,t.Marker=Qn,t.marker=ti,t.TileLayer=Bi,t.tileLayer=ji,t.GridLayer=Ai,t.gridLayer=Zi,t.SVG=Gi,t.svg=qi,t.Renderer=Di,t.Canvas=Fi,t.canvas=Wi,t.Path=ei,t.CircleMarker=ni,t.circleMarker=ii,t.Circle=oi,t.circle=ri,t.Polyline=si,t.polyline=ai,t.Polygon=ui,t.polygon=hi,t.Rectangle=Ki,t.rectangle=$i,t.Map=nn,t.map=on;var io=window.L;t.noConflict=function(){return window.L=io,this},window.L=t})},e11e2:function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},e4ae:function(t,e,n){var i=n("f772");t.exports=function(t){if(!i(t))throw TypeError(t+" is not an object!");return t}},e53d:function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},e6f3:function(t,e,n){var i=n("07e3"),o=n("36c3"),r=n("5b4e")(!1),s=n("5559")("IE_PROTO");t.exports=function(t,e){var n,a=o(t),u=0,h=[];for(n in a)n!=s&&i(a,n)&&h.push(n);while(e.length>u)i(a,n=e[u++])&&(~r(h,n)||h.push(n));return h}},ead6:function(t,e,n){var i=n("f772"),o=n("e4ae"),r=function(t,e){if(o(t),!i(e)&&null!==e)throw TypeError(e+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,e,i){try{i=n("d864")(Function.call,n("bf0b").f(Object.prototype,"__proto__").set,2),i(t,[]),e=!(t instanceof Array)}catch(o){e=!0}return function(t,n){return r(t,n),e?t.__proto__=n:i(t,n),t}}({},!1):void 0),check:r}},ebd6:function(t,e,n){var i=n("cb7c"),o=n("d8e8"),r=n("2b4c")("species");t.exports=function(t,e){var n,s=i(t).constructor;return void 0===s||void 0==(n=i(s)[r])?e:o(n)}},ebfd:function(t,e,n){var i=n("62a0")("meta"),o=n("f772"),r=n("07e3"),s=n("d9f6").f,a=0,u=Object.isExtensible||function(){return!0},h=!n("294c")(function(){return u(Object.preventExtensions({}))}),c=function(t){s(t,i,{value:{i:"O"+ ++a,w:{}}})},l=function(t,e){if(!o(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!r(t,i)){if(!u(t))return"F";if(!e)return"E";c(t)}return t[i].i},f=function(t,e){if(!r(t,i)){if(!u(t))return!0;if(!e)return!1;c(t)}return t[i].w},d=function(t){return h&&p.NEED&&u(t)&&!r(t,i)&&c(t),t},p=t.exports={KEY:i,NEED:!1,fastKey:l,getWeak:f,onFreeze:d}},f1ae:function(t,e,n){"use strict";var i=n("86cc"),o=n("4630");t.exports=function(t,e,n){e in t?i.f(t,e,o(0,n)):t[e]=n}},f201:function(t,e,n){var i=n("e4ae"),o=n("79aa"),r=n("5168")("species");t.exports=function(t,e){var n,s=i(t).constructor;return void 0===s||void 0==(n=i(s)[r])?e:o(n)}},f605:function(t,e){t.exports=function(t,e,n,i){if(!(t instanceof e)||void 0!==i&&i in t)throw TypeError(n+": incorrect invocation!");return t}},f751:function(t,e,n){var i=n("5ca1");i(i.S+i.F,"Object",{assign:n("7333")})},f772:function(t,e){t.exports=function(t){return"object"===typeof t?null!==t:"function"===typeof t}},f921:function(t,e,n){n("014b"),n("c207"),n("69d3"),n("765d"),t.exports=n("584a").Symbol},fa5b:function(t,e,n){t.exports=n("5537")("native-function-to-string",Function.toString)},fa99:function(t,e,n){n("0293"),t.exports=n("584a").Object.getPrototypeOf},fab2:function(t,e,n){var i=n("7726").document;t.exports=i&&i.documentElement},fdef:function(t,e){t.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"}}]); //# sourceMappingURL=chunk-vendors.js.map