├── .eslintignore
├── view
└── frontend
│ ├── web
│ ├── leaflet
│ │ ├── images
│ │ │ ├── layers.png
│ │ │ ├── layers-2x.png
│ │ │ ├── marker-icon.png
│ │ │ ├── marker-icon-2x.png
│ │ │ └── marker-shadow.png
│ │ ├── plugins
│ │ │ ├── markercluster
│ │ │ │ ├── MarkerCluster.css
│ │ │ │ ├── MarkerCluster.Default.css
│ │ │ │ └── leaflet.markercluster.js
│ │ │ └── geosearch
│ │ │ │ ├── l.geosearch.provider.esri.js
│ │ │ │ ├── l.geosearch.provider.bing.js
│ │ │ │ ├── l.geosearch.provider.nokia.js
│ │ │ │ ├── l.geosearch.provider.openstreetmap.js
│ │ │ │ ├── l.geosearch.provider.google.js
│ │ │ │ └── l.control.geosearch.js
│ │ ├── google-mutant.js
│ │ └── leaflet.css
│ └── js
│ │ ├── lib
│ │ └── mage
│ │ │ └── menu-mixin.js
│ │ ├── model
│ │ ├── markers.js
│ │ └── geoAddress.js
│ │ ├── listItemEvent.js
│ │ ├── map-provider
│ │ ├── osm.js
│ │ └── google-maps.js
│ │ ├── mapMobile.js
│ │ ├── polyfill
│ │ └── ie11
│ │ │ └── promise.min.js
│ │ ├── geocoder.js
│ │ ├── geocoder-provider
│ │ ├── osm.js
│ │ └── google.js
│ │ └── map.js
│ ├── layout
│ └── smile_map_styles.xml
│ └── requirejs-config.js
├── registration.php
├── Api
├── MapProviderInterface.php
├── Data
│ ├── GeoPointInterface.php
│ ├── GeolocalizedAddressInterface.php
│ └── AddressInterface.php
└── MapInterface.php
├── etc
├── module.xml
├── acl.xml
├── csp_whitelist.xml
├── di.xml
├── config.xml
└── adminhtml
│ └── system.xml
├── Model
├── Config
│ ├── Backend
│ │ └── MarkerIcon.php
│ └── Source
│ │ └── MapProvider.php
├── GeoPoint.php
├── GeolocalizedAddress.php
├── MapProvider.php
├── Map
│ └── DefaultMap.php
├── CountryInformationAcquirer.php
├── Address.php
└── AddressFormatter.php
├── ISSUE_TEMPLATE.md
├── composer.json
├── Helper
└── Map.php
└── .eslintrc
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*{.,-}min.js
2 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/images/layers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Smile-SA/magento2-module-map/HEAD/view/frontend/web/leaflet/images/layers.png
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/images/layers-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Smile-SA/magento2-module-map/HEAD/view/frontend/web/leaflet/images/layers-2x.png
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/images/marker-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Smile-SA/magento2-module-map/HEAD/view/frontend/web/leaflet/images/marker-icon.png
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/images/marker-icon-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Smile-SA/magento2-module-map/HEAD/view/frontend/web/leaflet/images/marker-icon-2x.png
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/images/marker-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Smile-SA/magento2-module-map/HEAD/view/frontend/web/leaflet/images/marker-shadow.png
--------------------------------------------------------------------------------
/registration.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/view/frontend/web/js/lib/mage/menu-mixin.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | 'jquery/ui'
4 | ], function ($) {
5 | 'use strict';
6 |
7 | return function (data) {
8 | $.widget('mage.menu', data.menu, {
9 | _create: function () {
10 | $(this.element).data('ui-menu', this);
11 | this._super();
12 | }
13 | });
14 |
15 | data.menu = $.mage.menu;
16 |
17 | return data;
18 | };
19 | });
--------------------------------------------------------------------------------
/Model/Config/Backend/MarkerIcon.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Model/GeoPoint.php:
--------------------------------------------------------------------------------
1 | latitude;
24 | }
25 |
26 | /**
27 | * @inheritdoc
28 | */
29 | public function getLongitude(): float
30 | {
31 | return $this->longitude;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/etc/acl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Api/Data/GeolocalizedAddressInterface.php:
--------------------------------------------------------------------------------
1 | getData(self::COORDINATES);
21 | }
22 |
23 | /**
24 | * @inheritdoc
25 | */
26 | public function setCoordinates(GeoPointInterface $coordinates): self
27 | {
28 | return $this->setData(self::COORDINATES, $coordinates);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Model/Config/Source/MapProvider.php:
--------------------------------------------------------------------------------
1 | mapProvider->getProviders() as $provider) {
27 | $result[] = ['value' => $provider->getIdentifier(), 'label' => $provider->getName()];
28 | }
29 |
30 | return $result;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/etc/csp_whitelist.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 | *.openstreetmap.org
8 | https://maps.googleapis.com
9 |
10 |
11 |
12 |
13 | *.openstreetmap.org
14 | https://maps.googleapis.com
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/view/frontend/web/js/listItemEvent.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | 'smile-map'
4 | ], function ($) {
5 | 'use strict';
6 | $.widget('smile.listItemEvent', {
7 |
8 | _create: function () {
9 |
10 | this.element.on('mouseover', function () {
11 | this.removeCurrentClass();
12 | this.showCurrentMarker();
13 | }.bind(this));
14 | this.element.on('mouseout', function () {
15 | this.removeCurrentClass();
16 | }.bind(this));
17 |
18 | },
19 |
20 | removeCurrentClass: function () {
21 | $('body').find('.custum-lf-popup').removeClass('current');
22 | },
23 |
24 | showCurrentMarker: function () {
25 | var name = this.element.attr('data-shop-name');
26 | $('.custum-lf-popup[data-n="' + name + '" ]').addClass('current');
27 | }
28 | });
29 |
30 | return $.smile.listItemEvent;
31 | });
32 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/plugins/markercluster/MarkerCluster.css:
--------------------------------------------------------------------------------
1 | .leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
2 | -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
3 | -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
4 | -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
5 | transition: transform 0.3s ease-out, opacity 0.3s ease-in;
6 | }
7 |
8 | .leaflet-cluster-spider-leg {
9 | /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
10 | -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
11 | -moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
12 | -o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
13 | transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
14 | }
15 |
--------------------------------------------------------------------------------
/view/frontend/web/js/map-provider/osm.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | 'leaflet'
4 | ], function ($, L) {
5 |
6 | Provider = {
7 | init: function(map, config, cb) {
8 | L.tileLayer(config['tile_url']).addTo(map);
9 | if (cb !== undefined) {
10 | cb(map);
11 | }
12 | },
13 |
14 | /**
15 | * Add distance from center of map to a given list of markers
16 | *
17 | * @param markersList
18 | * @param centerPosition
19 | * @returns {*}
20 | */
21 | addDistanceToMarkers: function (markersList, centerPosition) {
22 | var center = new L.LatLng(centerPosition.lat, centerPosition.lng);
23 | markersList.forEach(function(marker) {
24 | var itemPosition = new L.LatLng(marker.latitude, marker.longitude);
25 | marker.distance(itemPosition.distanceTo(center));
26 | }, this);
27 |
28 | return markersList;
29 | }
30 | };
31 |
32 | return Provider;
33 | });
34 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Preconditions
4 |
5 |
6 |
7 | Magento Version :
8 |
9 |
10 | Module Map Version :
11 |
12 |
13 | Environment :
14 |
15 |
16 | Third party modules :
17 |
18 | ### Steps to reproduce
19 |
20 | 1.
21 | 2.
22 | 3.
23 |
24 | ### Expected result
25 |
26 | 1.
27 |
28 | ### Actual result
29 |
30 | 1. [Screenshot, logs]
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Api/MapInterface.php:
--------------------------------------------------------------------------------
1 | mapHelper->getProviderIdentifier();
32 |
33 | if (!isset($this->mapProviders[$providerIdentifier])) {
34 | throw new LogicException(__("Map provider %s does not exists", $providerIdentifier));
35 | }
36 |
37 | return $this->mapProviders[$providerIdentifier];
38 | }
39 |
40 | /**
41 | * Get map providers.
42 | *
43 | * @return MapInterface[]
44 | */
45 | public function getProviders(): array
46 | {
47 | return $this->mapProviders;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/view/frontend/web/js/mapMobile.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | 'smile-map'
4 | ], function ($) {
5 | 'use strict';
6 | $.widget('smile.mapMobile', {
7 |
8 | _create: function () {
9 | var btnShowList = $('#display-store-list-mobile');
10 | var btnShowMap = $('#display-store-map-mobile');
11 | var map = $('.map');
12 |
13 | btnShowMap.on('click', function(e) {
14 | e.preventDefault();
15 | this.showMap(btnShowMap, btnShowList, map);
16 | }.bind(this));
17 |
18 | btnShowList.on('click', function(e) {
19 | e.preventDefault();
20 | this.showList(btnShowMap, btnShowList, map);
21 | }.bind(this));
22 | },
23 |
24 | showMap: function (btnShowMap, btnShowList, map) {
25 | map.addClass('map-open')
26 | btnShowMap.addClass('active');
27 | btnShowList.removeClass('active');
28 | },
29 |
30 | showList: function (btnShowMap, btnShowList, map) {
31 | map.removeClass('map-open')
32 | btnShowMap.removeClass('active');
33 | btnShowList.addClass('active');
34 | }
35 | });
36 |
37 | return $.smile.mapMobile;
38 | });
39 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/plugins/geosearch/l.geosearch.provider.esri.js:
--------------------------------------------------------------------------------
1 | /**
2 | * L.Control.GeoSearch - search for an address and zoom to it's location
3 | * L.GeoSearch.Provider.Esri uses arcgis geocoding service
4 | * https://github.com/smeijer/L.GeoSearch
5 | */
6 |
7 | L.GeoSearch.Provider.Esri = L.Class.extend({
8 | options: {
9 |
10 | },
11 |
12 | initialize: function(options) {
13 | options = L.Util.setOptions(this, options);
14 | },
15 |
16 | GetServiceUrl: function (qry) {
17 | var parameters = L.Util.extend({
18 | text: qry,
19 | f: 'pjson'
20 | }, this.options);
21 |
22 | return 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find'
23 | + L.Util.getParamString(parameters);
24 | },
25 |
26 | ParseJSON: function (data) {
27 | if (data.locations.length == 0)
28 | return [];
29 |
30 | var results = [];
31 | for (var i = 0; i < data.locations.length; i++)
32 | results.push(new L.GeoSearch.Result(
33 | data.locations[i].feature.geometry.x,
34 | data.locations[i].feature.geometry.y,
35 | data.locations[i].name
36 | ));
37 |
38 | return results;
39 | }
40 | });
41 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/plugins/geosearch/l.geosearch.provider.bing.js:
--------------------------------------------------------------------------------
1 | /**
2 | * L.Control.GeoSearch - search for an address and zoom to it's location
3 | * L.GeoSearch.Provider.Bing uses bing geocoding service
4 | * https://github.com/smeijer/L.GeoSearch
5 | */
6 |
7 | L.GeoSearch.Provider.Bing = L.Class.extend({
8 | options: {
9 |
10 | },
11 |
12 | initialize: function(options) {
13 | options = L.Util.setOptions(this, options);
14 | },
15 |
16 | GetServiceUrl: function (qry) {
17 | var parameters = L.Util.extend({
18 | query: qry,
19 | jsonp: 'parseLocation'
20 | }, this.options);
21 |
22 | return 'https://dev.virtualearth.net/REST/v1/Locations'
23 | + L.Util.getParamString(parameters);
24 | },
25 |
26 | ParseJSON: function (data) {
27 | if (data.resourceSets.length == 0 || data.resourceSets[0].resources.length == 0)
28 | return [];
29 |
30 | var results = [];
31 | for (var i = 0; i < data.resourceSets[0].resources.length; i++)
32 | results.push(new L.GeoSearch.Result(
33 | data.resourceSets[0].resources[i].point.coordinates[1],
34 | data.resourceSets[0].resources[i].point.coordinates[0],
35 | data.resourceSets[0].resources[i].address.formattedAddress
36 | ));
37 |
38 | return results;
39 | }
40 | });
41 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/plugins/geosearch/l.geosearch.provider.nokia.js:
--------------------------------------------------------------------------------
1 | /**
2 | * L.Control.GeoSearch - search for an address and zoom to it's location
3 | * L.GeoSearch.Provider.Nokia uses Nokia geocoding service
4 | * https://github.com/smeijer/L.GeoSearch
5 | */
6 |
7 | L.GeoSearch.Provider.Nokia = L.Class.extend({
8 | options: {
9 |
10 | },
11 |
12 | initialize: function(options) {
13 | options = L.Util.setOptions(this, options);
14 | },
15 |
16 | GetServiceUrl: function (qry) {
17 | var parameters = L.Util.extend({
18 | searchtext: qry,
19 | jsoncallback: 'parseLocation'
20 | }, this.options);
21 |
22 | return 'http://geo.nlp.nokia.com/search/6.2/geocode.json'
23 | + L.Util.getParamString(parameters);
24 | },
25 |
26 | ParseJSON: function (data) {
27 | if (data.Response.View.length == 0 || data.Response.View[0].Result.length == 0)
28 | return [];
29 |
30 | var results = [];
31 | for (var i = 0; i < data.Response.View[0].Result.length; i++)
32 | results.push(new L.GeoSearch.Result(
33 | data.Response.View[0].Result[i].Location.DisplayPosition.Longitude,
34 | data.Response.View[0].Result[i].Location.DisplayPosition.Latitude,
35 | data.Response.View[0].Result[i].Location.Address.Label
36 | ));
37 |
38 | return results;
39 | }
40 | });
41 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "smile/module-map",
3 | "type": "magento2-module",
4 | "description": "Smile Map Utilities Module",
5 | "keywords": ["magento2", "map", "geocoding", "geolocalisation"],
6 | "license": "OSL-3.0",
7 | "authors": [
8 | {
9 | "name": "Aurélien Foucret",
10 | "email": "aurelien.foucret@smile.fr"
11 | },
12 | {
13 | "name": "Romain Ruaud",
14 | "email": "romain.ruaud@smile.fr"
15 | }
16 | ],
17 | "require": {
18 | "php": "^8.1",
19 | "magento/framework": ">=103.0.4",
20 | "magento/module-store": ">=101.1.4",
21 | "willdurand/geocoder": "^4.4"
22 | },
23 | "require-dev": {
24 | "smile/magento2-smilelab-quality-suite": "^3.0"
25 | },
26 | "repositories": [
27 | {
28 | "type": "composer",
29 | "url": "https://repo.magento.com/"
30 | }
31 | ],
32 | "minimum-stability": "dev",
33 | "prefer-stable": true,
34 | "autoload": {
35 | "files": [
36 | "registration.php"
37 | ],
38 | "psr-4": {
39 | "Smile\\Map\\": ""
40 | }
41 | },
42 | "config": {
43 | "allow-plugins": {
44 | "magento/composer-dependency-version-audit-plugin": true,
45 | "dealerdirect/phpcodesniffer-composer-installer": true
46 | },
47 | "sort-packages": true
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/plugins/markercluster/MarkerCluster.Default.css:
--------------------------------------------------------------------------------
1 | .marker-cluster-small {
2 | background-color: rgba(181, 226, 140, 0.6);
3 | }
4 | .marker-cluster-small div {
5 | background-color: rgba(110, 204, 57, 0.6);
6 | }
7 |
8 | .marker-cluster-medium {
9 | background-color: rgba(241, 211, 87, 0.6);
10 | }
11 | .marker-cluster-medium div {
12 | background-color: rgba(240, 194, 12, 0.6);
13 | }
14 |
15 | .marker-cluster-large {
16 | background-color: rgba(253, 156, 115, 0.6);
17 | }
18 | .marker-cluster-large div {
19 | background-color: rgba(241, 128, 23, 0.6);
20 | }
21 |
22 | /* IE 6-8 fallback colors */
23 | .leaflet-oldie .marker-cluster-small {
24 | background-color: rgb(181, 226, 140);
25 | }
26 | .leaflet-oldie .marker-cluster-small div {
27 | background-color: rgb(110, 204, 57);
28 | }
29 |
30 | .leaflet-oldie .marker-cluster-medium {
31 | background-color: rgb(241, 211, 87);
32 | }
33 | .leaflet-oldie .marker-cluster-medium div {
34 | background-color: rgb(240, 194, 12);
35 | }
36 |
37 | .leaflet-oldie .marker-cluster-large {
38 | background-color: rgb(253, 156, 115);
39 | }
40 | .leaflet-oldie .marker-cluster-large div {
41 | background-color: rgb(241, 128, 23);
42 | }
43 |
44 | .marker-cluster {
45 | background-clip: padding-box;
46 | border-radius: 20px;
47 | }
48 | .marker-cluster div {
49 | width: 30px;
50 | height: 30px;
51 | margin-left: 5px;
52 | margin-top: 5px;
53 |
54 | text-align: center;
55 | border-radius: 15px;
56 | font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
57 | }
58 | .marker-cluster span {
59 | line-height: 30px;
60 | }
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/plugins/geosearch/l.geosearch.provider.openstreetmap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * L.Control.GeoSearch - search for an address and zoom to it's location
3 | * L.GeoSearch.Provider.OpenStreetMap uses openstreetmap geocoding service
4 | * https://github.com/smeijer/L.GeoSearch
5 | */
6 |
7 | L.GeoSearch.Provider.OpenStreetMap = L.Class.extend({
8 | options: {},
9 |
10 | initialize: function(options) {
11 | options = L.Util.setOptions(this, options);
12 | },
13 |
14 | GetServiceUrl: function (qry) {
15 | var parameters = L.Util.extend({
16 | q: qry,
17 | format: 'json'
18 | }, this.options);
19 |
20 | return 'https:'
21 | + '//nominatim.openstreetmap.org/search'
22 | + L.Util.getParamString(parameters);
23 | },
24 |
25 | ParseJSON: function (data) {
26 | var results = [];
27 |
28 | for (var i = 0; i < data.length; i++) {
29 | var boundingBox = data[i].boundingbox,
30 | northEastLatLng = new L.LatLng( boundingBox[1], boundingBox[3] ),
31 | southWestLatLng = new L.LatLng( boundingBox[0], boundingBox[2] );
32 |
33 | if (data[i].address)
34 | data[i].address.type = data[i].type;
35 |
36 | results.push(new L.GeoSearch.Result(
37 | data[i].lon,
38 | data[i].lat,
39 | data[i].display_name,
40 | new L.LatLngBounds([
41 | northEastLatLng,
42 | southWestLatLng
43 | ]),
44 | data[i].address
45 | ));
46 | }
47 |
48 | return results;
49 | }
50 | });
51 |
--------------------------------------------------------------------------------
/Model/Map/DefaultMap.php:
--------------------------------------------------------------------------------
1 | config = $mapHelper->getProviderConfiguration($identifier);
27 | }
28 |
29 | /**
30 | * @inheritdoc
31 | */
32 | public function getConfig(): array
33 | {
34 | return $this->config;
35 | }
36 |
37 | /**
38 | * @inheritdoc
39 | */
40 | public function getIdentifier(): string
41 | {
42 | return $this->identifier;
43 | }
44 |
45 | /**
46 | * @inheritdoc
47 | */
48 | public function getName(): string
49 | {
50 | return $this->name;
51 | }
52 |
53 | /**
54 | * @inheritdoc
55 | */
56 | public function getDirectionUrl(GeoPointInterface $dest, ?GeoPointInterface $orig = null): string
57 | {
58 | $urlTemplate = $this->config['direction_url_template'];
59 |
60 | $data = new DataObject();
61 |
62 | $data->setDestLatitude($dest->getLatitude());
63 | $data->setDestLongitude($dest->getLongitude());
64 |
65 | if ($orig !== null) {
66 | $data->setHasOrigin(true);
67 | $data->setOrigLatitude($orig->getLatitude());
68 | $data->setOrigLongitude($orig->getLongitude());
69 | }
70 |
71 | return $this->filterManager->template($urlTemplate, ['variables' => $data->toArray()]);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/etc/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | osm
11 | OpenStreetMap
12 |
13 |
14 |
15 |
16 |
17 | google
18 | Google Maps
19 |
20 |
21 |
22 |
23 |
24 |
25 | - Smile\Map\Model\Map\Osm
26 | - Smile\Map\Model\Map\Google
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Smile\Map\Model\CountryInformationAcquirer
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Model/CountryInformationAcquirer.php:
--------------------------------------------------------------------------------
1 | storeManager->getStore();
46 | $storeLocale = $this->scopeConfig->getValue(
47 | 'general/locale/code',
48 | ScopeInterface::SCOPE_STORES,
49 | $store->getCode()
50 | );
51 |
52 | $countriesCollection = $this->countryCollection->load();
53 | $regions = $this->directoryHelper->getRegionData();
54 | $country = $countriesCollection->getItemById($countryId);
55 | if (!$country) {
56 | throw new NoSuchEntityException(__('Requested country is not available.'));
57 | }
58 |
59 | return $this->setCountryInfo($country, $regions, $storeLocale);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/view/frontend/web/js/map-provider/google-maps.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | 'leaflet',
4 | 'google-mutant',
5 | 'leaflet-geosearch',
6 | 'leaflet-geosearch-google'
7 | ], function ($, L) {
8 |
9 | /**
10 | * Retrieve Google Maps API
11 | *
12 | * @param config the config (contains API Key, country, etc...).
13 | *
14 | * @returns {string}
15 | */
16 | function getApiUrl(config) {
17 | var apiKey = config['api_key'];
18 | var country = config['country'] || 'FR';
19 | var locale = config['locale'] || 'fr_FR';
20 | var libraries = config['libraries'] || 'geometry';
21 |
22 | return '//maps.google.com/maps/api/js?key=' + apiKey + '&libraries=' + libraries + '&language=' + locale + '&country=' + country;
23 | }
24 |
25 | function addGoogleMapsLayer(map, config) {
26 | var mutantConfig = {type: config['type'] || 'roadmap'};
27 | if (config['map_styles'] && (config['map_styles'] !== '')) {
28 | mutantConfig.styles = JSON.parse(config['map_styles'].replace(/(\r\n|\n|\r)/gm,""));
29 | }
30 | L.gridLayer.googleMutant(mutantConfig).addTo(map);
31 | }
32 |
33 | Provider = {
34 | init: function(map, config, callback) {
35 | require([getApiUrl(config)], function() {
36 | if (map !== null) {
37 | addGoogleMapsLayer(map, config);
38 | }
39 | if (callback !== undefined) {
40 | callback(map);
41 | }
42 | });
43 | },
44 |
45 | /**
46 | * Add distance from center of map to a given list of markers
47 | *
48 | * @param markersList
49 | * @param centerPosition
50 | * @returns {*}
51 | */
52 | addDistanceToMarkers: function (markersList, centerPosition) {
53 | var center = new google.maps.LatLng(centerPosition.lat, centerPosition.lng);
54 | markersList.forEach(function(marker) {
55 | var itemPosition = new google.maps.LatLng(marker.latitude, marker.longitude);
56 | marker.distance(google.maps.geometry.spherical.computeDistanceBetween(itemPosition, center));
57 | }, this);
58 |
59 | return markersList;
60 | }
61 | };
62 |
63 | return Provider;
64 | });
65 |
--------------------------------------------------------------------------------
/view/frontend/requirejs-config.js:
--------------------------------------------------------------------------------
1 | var config = {
2 | map: {
3 | '*': {
4 | 'smile-map' : 'Smile_Map/js/map',
5 | 'smile-map-markers' : 'Smile_Map/js/model/markers',
6 | 'smile-map-provider-osm' : 'Smile_Map/js/map-provider/osm',
7 | 'smile-map-provider-google' : 'Smile_Map/js/map-provider/google-maps',
8 | 'smile-geocoder' : 'Smile_Map/js/geocoder',
9 | 'smile-geocoder-provider-osm' : 'Smile_Map/js/geocoder-provider/osm',
10 | 'smile-geocoder-provider-google' : 'Smile_Map/js/geocoder-provider/google',
11 | 'listItemEvent' : 'Smile_Map/js/listItemEvent',
12 | 'mapMobile' : 'Smile_Map/js/mapMobile',
13 | 'promotionSlider' : 'Smile_Map/js/promotionSlider',
14 | 'geoAddressModel' : 'Smile_Map/js/model/geoAddress'
15 | }
16 | },
17 | config: {
18 | mixins: {
19 | 'mage/menu': {
20 | 'Smile_Map/js/lib/mage/menu-mixin': true
21 | }
22 | }
23 | },
24 | paths: {
25 | 'leaflet' : 'Smile_Map/leaflet/leaflet',
26 | 'leaflet-geosearch' : 'Smile_Map/leaflet/plugins/geosearch/l.control.geosearch',
27 | 'leaflet-geosearch-osm' : 'Smile_Map/leaflet/plugins/geosearch/l.geosearch.provider.openstreetmap',
28 | 'leaflet-geosearch-google' : 'Smile_Map/leaflet/plugins/geosearch/l.geosearch.provider.google',
29 | 'leaflet-markercluster' : 'Smile_Map/leaflet/plugins/markercluster/leaflet.markercluster',
30 | 'google-mutant' : 'Smile_Map/leaflet/google-mutant'
31 | },
32 | shim: {
33 | 'leaflet': {
34 | exports : 'L'
35 | },
36 | 'leaflet-geosearch': {
37 | deps: ['leaflet']
38 | },
39 | 'leaflet-geosearch-osm': {
40 | deps: ['leaflet', 'leaflet-geosearch']
41 | },
42 | 'leaflet-geosearch-google': {
43 | deps: ['leaflet', 'leaflet-geosearch']
44 | },
45 | 'leaflet-markercluster': {
46 | deps: ['leaflet']
47 | },
48 | 'google-mutant': {
49 | deps: ['leaflet']
50 | },
51 | 'smile-map-provider-osm': {
52 | deps: ['leaflet-geosearch-osm']
53 | },
54 | 'smile-map-provider-google': {
55 | deps: ['google-mutant', 'leaflet-geosearch-google']
56 | }
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/view/frontend/web/js/polyfill/ie11/promise.min.js:
--------------------------------------------------------------------------------
1 | !function(e){function n(){}function t(e,n){return function(){e.apply(n,arguments)}}function o(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],s(e,this)}function i(e,n){for(;3===e._state;)e=e._value;return 0===e._state?void e._deferreds.push(n):(e._handled=!0,void o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null===t)return void(1===e._state?r:u)(n.promise,e._value);var o;try{o=t(e._value)}catch(i){return void u(n.promise,i)}r(n.promise,o)}))}function r(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var i=n.then;if(n instanceof o)return e._state=3,e._value=n,void f(e);if("function"==typeof i)return void s(t(i,n),e)}e._state=1,e._value=n,f(e)}catch(r){u(e,r)}}function u(e,n){e._state=2,e._value=n,f(e)}function f(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;n
2 |
3 |
4 |
5 |
18 |
19 |
20 | {{if street1}}{{var street1}}
21 | {{/if}}
22 | {{depend street2}}{{var street2}}{{/depend}}
23 | {{depend street3}}{{var street3}}{{/depend}}
24 | {{depend street4}}{{var street4}}{{/depend}}
25 | {{if city}}{{var city}}, {{/if}}{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}}
26 | {{var country}}
27 | {{var street}}, {{var city}}, {{var region}} {{var postcode}}, {{var country}}
28 | {{/if}}
29 | {{depend street2}}{{var street2}}
{{/depend}}
30 | {{depend street3}}{{var street3}}
{{/depend}}
31 | {{depend street4}}{{var street4}}
{{/depend}}
32 | {{if city}}{{var city}}, {{/if}}{{if region}}{{var region}}, {{/if}}{{if postcode}}{{var postcode}}{{/if}}
33 | {{var country}}
34 | ]]>
35 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Api/Data/AddressInterface.php:
--------------------------------------------------------------------------------
1 | getData(self::REGION);
24 | }
25 |
26 | /**
27 | * @inheritdoc
28 | */
29 | public function getRegionId(): ?int
30 | {
31 | return (int) $this->getData(self::REGION_ID);
32 | }
33 |
34 | /**
35 | * @inheritdoc
36 | */
37 | public function getCountryId(): ?string
38 | {
39 | return $this->getData(self::COUNTRY_ID);
40 | }
41 |
42 | /**
43 | * @inheritdoc
44 | */
45 | public function getStreet(): array
46 | {
47 | return is_array($this->getData(self::STREET))
48 | ? $this->getData(self::STREET)
49 | : [$this->getData(self::STREET)];
50 | }
51 |
52 | /**
53 | * @inheritdoc
54 | */
55 | public function getPostcode(): ?string
56 | {
57 | return $this->getData(self::POSTCODE);
58 | }
59 |
60 | /**
61 | * @inheritdoc
62 | */
63 | public function getCity(): ?string
64 | {
65 | return $this->getData(self::CITY);
66 | }
67 |
68 | /**
69 | * Set retailer id.
70 | */
71 | public function setRetailerId(int $retailerId): self
72 | {
73 | return $this->setData(self::RETAILER_ID_FIELD, $retailerId);
74 | }
75 |
76 | /**
77 | * @inheritdoc
78 | */
79 | public function setCountryId(string $countryId): self
80 | {
81 | return $this->setData(self::COUNTRY_ID, $countryId);
82 | }
83 |
84 | /**
85 | * @inheritdoc
86 | */
87 | public function setRegion(?string $region = null): self
88 | {
89 | return $this->setData(self::REGION, $region);
90 | }
91 |
92 | /**
93 | * @inheritdoc
94 | */
95 | public function setRegionId(int $regionId): self
96 | {
97 | return $this->setData(self::REGION_ID, $regionId);
98 | }
99 |
100 | /**
101 | * @inheritdoc
102 | */
103 | public function setStreet(array|string $street): self
104 | {
105 | return $this->setData(self::STREET, $street);
106 | }
107 |
108 | /**
109 | * @inheritdoc
110 | */
111 | public function setPostcode(string $postcode): self
112 | {
113 | return $this->setData(self::POSTCODE, $postcode);
114 | }
115 |
116 | /**
117 | * @inheritdoc
118 | */
119 | public function setCity(string $city): self
120 | {
121 | return $this->setData(self::CITY, $city);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/plugins/geosearch/l.geosearch.provider.google.js:
--------------------------------------------------------------------------------
1 | /**
2 | * L.Control.GeoSearch - search for an address and zoom to it's location
3 | * L.GeoSearch.Provider.Google uses google geocoding service
4 | * https://github.com/smeijer/L.GeoSearch
5 | */
6 |
7 | onLoadGoogleApiCallback = function() {
8 | L.GeoSearch.Provider.Google.Geocoder = new google.maps.Geocoder();
9 | var scriptNode = document.getElementById('load_google_api');
10 | if (!!scriptNode) {
11 | document.body.removeChild(scriptNode);
12 | }
13 | };
14 |
15 | L.GeoSearch.Provider.Google = L.Class.extend({
16 | _isReady: false,
17 | _onReadyQueue: [],
18 |
19 | options: {
20 |
21 | },
22 |
23 | initialize: function(options) {
24 | options = L.Util.setOptions(this, options);
25 | if (!window.google || !window.google.maps) {
26 | this.loadMapsApi();
27 | } else {
28 | // if google is already loaded, make sure we initialize the Geocoder
29 | onLoadGoogleApiCallback();
30 | this._isReady = true;
31 | }
32 |
33 | },
34 |
35 | loadMapsApi: function () {
36 | var key = (typeof(this.options.key) !== 'undefined')? this.options.key : "YOUR_API_KEY";
37 | var self = this;
38 | var url = "https://maps.googleapis.com/maps/api/js?key="+key+"&v=3&callback=onLoadGoogleApiCallback&sensor=false";
39 | var script = document.createElement('script');
40 | script.id = 'load_google_api';
41 | script.type = "text/javascript";
42 | script.src = url;
43 | document.body.appendChild(script);
44 |
45 | var handle = setInterval(function() {
46 | if (typeof google !== 'undefined' && L.GeoSearch.Provider.Google.Geocoder instanceof google.maps.Geocoder) {
47 | clearInterval(handle);
48 | self._onReady();
49 | }
50 | }, 25);
51 | },
52 |
53 | _onReady: function() {
54 | this._isReady = true;
55 |
56 | var data;
57 | while (data = this._onReadyQueue.shift()) {
58 | this.GetLocations(data.qry, data.callback);
59 | }
60 | },
61 |
62 | GetLocations: function(qry, callback) {
63 | if (!this._isReady) {
64 | // store calls to this method, so the can be re-invoked once
65 | // the google api is loaded
66 | this._onReadyQueue.push({ qry: qry, callback: callback });
67 | return;
68 | }
69 |
70 | var geocoder = L.GeoSearch.Provider.Google.Geocoder;
71 |
72 | var parameters = L.Util.extend({
73 | address: qry
74 | }, this.options);
75 |
76 | var results = geocoder.geocode(parameters, function(data){
77 | data = {results: data};
78 |
79 | var results = [],
80 | northEastLatLng,
81 | southWestLatLng,
82 | bounds;
83 | for (var i = 0; i < data.results.length; i++) {
84 |
85 | if( data.results[i].geometry.bounds ) {
86 | var northEastGoogle = data.results[i].geometry.bounds.getNorthEast(),
87 | southWestGoogle = data.results[i].geometry.bounds.getSouthWest();
88 |
89 | northEastLatLng = new L.LatLng( northEastGoogle.lat(), northEastGoogle.lng() );
90 | southWestLatLng = new L.LatLng( southWestGoogle.lat(), southWestGoogle.lng() );
91 | bounds = new L.LatLngBounds([ northEastLatLng, southWestLatLng ]);
92 | }
93 | else {
94 | bounds = undefined;
95 | }
96 | results.push(new L.GeoSearch.Result(
97 | data.results[i].geometry.location.lng(),
98 | data.results[i].geometry.location.lat(),
99 | data.results[i].formatted_address,
100 | bounds
101 | ));
102 | }
103 |
104 | if(typeof callback == 'function')
105 | callback(results);
106 | });
107 | },
108 | });
109 |
--------------------------------------------------------------------------------
/Model/AddressFormatter.php:
--------------------------------------------------------------------------------
1 | storeManager->getStore()->getId();
51 | }
52 |
53 | $template = $this->getAddressTemplate($format, $storeId);
54 | $variables = $this->getVariables($address);
55 |
56 | return $this->filterManager->template($template, ['variables' => $variables]);
57 | }
58 |
59 | /**
60 | * Extract variables used into templates.
61 | *
62 | * @throws NoSuchEntityException
63 | */
64 | private function getVariables(AddressInterface $address): array
65 | {
66 | // @phpstan-ignore-next-line
67 | $variables = $address->getData();
68 |
69 | if ($address->getStreet()) {
70 | foreach ($address->getStreet() as $index => $streetLine) {
71 | ++$index;
72 | $variables["street{$index}"] = $streetLine;
73 | }
74 |
75 | $variables['street'] = implode(" ", $address->getStreet());
76 | }
77 |
78 | if ($address->getCountryId()) {
79 | $countryId = $address->getCountryId();
80 | $variables['country'] = $this->getCountryFullName($countryId);
81 | }
82 |
83 | return $variables;
84 | }
85 |
86 | /**
87 | * Load template from the configuration.
88 | */
89 | private function getAddressTemplate(string $format, int $storeId): string
90 | {
91 | $path = self::FORMAT_XML_BASE_XPATH . '/' . $format;
92 |
93 | return $this->scopeConfig->getValue($path, ScopeInterface::SCOPE_STORE, $storeId);
94 | }
95 |
96 | /**
97 | * Retrieve Country name for current locale, from local cache if possible, or from previous calculation.
98 | * This is mainly due to the fact that calling CountryInformationAcquirerInterface::getCountryInfo processes a full
99 | * loading of directory data, without using any cache.
100 | *
101 | * @throws NoSuchEntityException
102 | */
103 | private function getCountryFullName(string $countryId): mixed
104 | {
105 | $store = $this->storeManager->getStore();
106 | $storeLocale = $this->scopeConfig->getValue(
107 | 'general/locale/code',
108 | ScopeInterface::SCOPE_STORES,
109 | $store->getCode()
110 | );
111 |
112 | $cacheKey = sprintf("%s_%s_%s", $countryId, $store->getId(), $storeLocale);
113 |
114 | if (!isset($this->localCache[$cacheKey])) {
115 | $data = $this->cacheInterface->load($cacheKey);
116 |
117 | if (!$data) {
118 | $data = $this->countryInfo->getCountryInfo($countryId)->getFullNameLocale();
119 | $this->cacheInterface->save(
120 | $data,
121 | $cacheKey,
122 | [Config::TYPE_IDENTIFIER],
123 | 7200
124 | );
125 | }
126 |
127 | $this->localCache[$cacheKey] = $data;
128 | }
129 |
130 | return $this->localCache[$cacheKey];
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/Helper/Map.php:
--------------------------------------------------------------------------------
1 | mediaDirectory = $this->fileSystem->getDirectoryRead(DirectoryList::MEDIA);
40 | parent::__construct($context);
41 | }
42 |
43 | /**
44 | * Returns currently configured map provider.
45 | */
46 | public function getProviderIdentifier(): string
47 | {
48 | return $this->scopeConfig->getValue(self::MAP_CONFIG_XML_PATH . '/provider');
49 | }
50 |
51 | /**
52 | * Returns map configuration by provider.
53 | */
54 | public function getProviderConfiguration(string $providerIdentifier): array
55 | {
56 | $config = [];
57 |
58 | $mapKeyFunc = function (&$value, $key) use (&$config, $providerIdentifier): void {
59 | if (
60 | str_starts_with($key, 'provider_' . $providerIdentifier)
61 | || str_starts_with($key, 'provider_' . self::SHARED_SETTINGS_NAME)
62 | ) {
63 | $prefixes = ['provider_' . $providerIdentifier . '_', 'provider_' . self::SHARED_SETTINGS_NAME . '_'];
64 | $key = str_replace($prefixes, '', $key);
65 | $config[$key] = $value;
66 | }
67 | };
68 |
69 | $allConfig = $this->scopeConfig->getValue(
70 | self::MAP_CONFIG_XML_PATH,
71 | 'store',
72 | $this->storeManager->getStore()->getCode()
73 | );
74 |
75 | array_walk($allConfig, $mapKeyFunc);
76 |
77 | if (!isset($config['country'])) {
78 | $config['country'] = $this->scopeConfig->getValue('general/country/default', ScopeInterface::SCOPE_STORES);
79 | }
80 |
81 | if (!isset($config['locale'])) {
82 | $config['locale'] = $this->localeResolver->getLocale();
83 | }
84 |
85 | $config['markerIcon'] = $this->getMarkerIcon($config);
86 |
87 | return $config;
88 | }
89 |
90 | /**
91 | * Retrieve custom marker icon to use, if any. Otherwise, returns default leaflet marker.
92 | */
93 | private function getMarkerIcon(array $config): string
94 | {
95 | $folderName = MarkerIcon::UPLOAD_DIR;
96 | $storeLogoPath = $config['markerIcon'] ?? null;
97 | $path = $folderName . '/' . $storeLogoPath;
98 |
99 | $logoUrl = $this->_urlBuilder->getBaseUrl(['_type' => UrlInterface::URL_TYPE_MEDIA]) . $path;
100 |
101 | try {
102 | $defaultFile = "Smile_Map::leaflet/images/marker-icon.png";
103 | $params = ['_secure' => $this->_getRequest()->isSecure()];
104 | $url = $this->assetRepository->getUrlWithParams($defaultFile, $params);
105 | } catch (LocalizedException $e) {
106 | $this->_logger->critical($e->getMessage());
107 | $url = $this->_urlBuilder->getUrl('', ['_direct' => 'core/index/notFound']);
108 | }
109 |
110 | if ($storeLogoPath !== null && $this->isFile($path)) {
111 | $url = $logoUrl;
112 | }
113 |
114 | return $url;
115 | }
116 |
117 | /**
118 | * If DB file storage is on - find there, otherwise - just file_exists.
119 | */
120 | private function isFile(string $filename): bool
121 | {
122 | if ($this->fileStorageHelper->checkDbUsage() && !$this->getMediaDirectory()->isFile($filename)) {
123 | $this->fileStorageHelper->saveFileToFilesystem($filename);
124 | }
125 |
126 | return $this->getMediaDirectory()->isFile($filename);
127 | }
128 |
129 | /**
130 | * Get media directory.
131 | */
132 | private function getMediaDirectory(): ReadInterface
133 | {
134 | return $this->mediaDirectory;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | ecmaFeatures:
2 | modules: true
3 | jsx: true
4 |
5 | env:
6 | amd: true
7 | browser: true
8 | es6: true
9 | jquery: true
10 | node: true
11 |
12 | # http://eslint.org/docs/rules/
13 | rules:
14 | # Possible Errors
15 | comma-dangle: [2, never]
16 | no-cond-assign: 2
17 | no-console: 0
18 | no-constant-condition: 2
19 | no-control-regex: 2
20 | no-debugger: 2
21 | no-dupe-args: 2
22 | no-dupe-keys: 2
23 | no-duplicate-case: 2
24 | no-empty: 2
25 | no-empty-character-class: 2
26 | no-ex-assign: 2
27 | no-extra-boolean-cast: 2
28 | no-extra-parens: 0
29 | no-extra-semi: 2
30 | no-func-assign: 2
31 | no-inner-declarations: [2, functions]
32 | no-invalid-regexp: 2
33 | no-irregular-whitespace: 2
34 | no-negated-in-lhs: 2
35 | no-obj-calls: 2
36 | no-regex-spaces: 2
37 | no-sparse-arrays: 2
38 | no-unexpected-multiline: 2
39 | no-unreachable: 2
40 | use-isnan: 2
41 | valid-jsdoc: 0
42 | valid-typeof: 2
43 |
44 | # Best Practices
45 | accessor-pairs: 2
46 | block-scoped-var: 0
47 | complexity: [2, 6]
48 | consistent-return: 0
49 | curly: 0
50 | default-case: 0
51 | dot-location: 0
52 | dot-notation: 0
53 | eqeqeq: 2
54 | guard-for-in: 2
55 | no-alert: 2
56 | no-caller: 2
57 | no-case-declarations: 2
58 | no-div-regex: 2
59 | no-else-return: 0
60 | no-empty-label: 2
61 | no-empty-pattern: 2
62 | no-eq-null: 2
63 | no-eval: 2
64 | no-extend-native: 2
65 | no-extra-bind: 2
66 | no-fallthrough: 2
67 | no-floating-decimal: 0
68 | no-implicit-coercion: 0
69 | no-implied-eval: 2
70 | no-invalid-this: 0
71 | no-iterator: 2
72 | no-labels: 0
73 | no-lone-blocks: 2
74 | no-loop-func: 2
75 | no-magic-number: 0
76 | no-multi-spaces: 0
77 | no-multi-str: 0
78 | no-native-reassign: 2
79 | no-new-func: 2
80 | no-new-wrappers: 2
81 | no-new: 2
82 | no-octal-escape: 2
83 | no-octal: 2
84 | no-proto: 2
85 | no-redeclare: 2
86 | no-return-assign: 2
87 | no-script-url: 2
88 | no-self-compare: 2
89 | no-sequences: 0
90 | no-throw-literal: 0
91 | no-unused-expressions: 2
92 | no-useless-call: 2
93 | no-useless-concat: 2
94 | no-void: 2
95 | no-warning-comments: 0
96 | no-with: 2
97 | radix: 2
98 | vars-on-top: 0
99 | wrap-iife: 2
100 | yoda: 0
101 |
102 | # Strict
103 | strict: 0
104 |
105 | # Variables
106 | init-declarations: 0
107 | no-catch-shadow: 2
108 | no-delete-var: 2
109 | no-label-var: 2
110 | no-shadow-restricted-names: 2
111 | no-shadow: 0
112 | no-undef-init: 2
113 | no-undef: 0
114 | no-undefined: 0
115 | no-unused-vars: 0
116 | no-use-before-define: 0
117 |
118 | # Node.js and CommonJS
119 | callback-return: 2
120 | global-require: 2
121 | handle-callback-err: 2
122 | no-mixed-requires: 0
123 | no-new-require: 0
124 | no-path-concat: 2
125 | no-process-exit: 2
126 | no-restricted-modules: 0
127 | no-sync: 0
128 |
129 | # Stylistic Issues
130 | array-bracket-spacing: 0
131 | block-spacing: 0
132 | brace-style: 0
133 | camelcase: 0
134 | comma-spacing: 0
135 | comma-style: 0
136 | computed-property-spacing: 0
137 | consistent-this: 0
138 | eol-last: 0
139 | func-names: 0
140 | func-style: 0
141 | id-length: 0
142 | id-match: 0
143 | indent: 0
144 | jsx-quotes: 0
145 | key-spacing: 0
146 | linebreak-style: 0
147 | lines-around-comment: 0
148 | max-depth: 0
149 | max-len: 0
150 | max-nested-callbacks: 0
151 | max-params: 0
152 | max-statements: [2, 30]
153 | new-cap: 0
154 | new-parens: 0
155 | newline-after-var: 0
156 | no-array-constructor: 0
157 | no-bitwise: 0
158 | no-continue: 0
159 | no-inline-comments: 0
160 | no-lonely-if: 0
161 | no-mixed-spaces-and-tabs: 0
162 | no-multiple-empty-lines: 0
163 | no-negated-condition: 0
164 | no-nested-ternary: 0
165 | no-new-object: 0
166 | no-plusplus: 0
167 | no-restricted-syntax: 0
168 | no-spaced-func: 0
169 | no-ternary: 0
170 | no-trailing-spaces: 0
171 | no-underscore-dangle: 0
172 | no-unneeded-ternary: 0
173 | object-curly-spacing: 0
174 | one-var: 0
175 | operator-assignment: 0
176 | operator-linebreak: 0
177 | padded-blocks: 0
178 | quote-props: 0
179 | quotes: 0
180 | require-jsdoc: 0
181 | semi-spacing: 0
182 | semi: 0
183 | sort-vars: 0
184 | space-after-keywords: 0
185 | space-before-blocks: 0
186 | space-before-function-paren: 0
187 | space-before-keywords: 0
188 | space-in-parens: 0
189 | space-infix-ops: 0
190 | space-return-throw-case: 0
191 | space-unary-ops: 0
192 | spaced-comment: 0
193 | wrap-regex: 0
194 |
195 | # ECMAScript 6
196 | arrow-body-style: 0
197 | arrow-parens: 0
198 | arrow-spacing: 0
199 | constructor-super: 0
200 | generator-star-spacing: 0
201 | no-arrow-condition: 0
202 | no-class-assign: 0
203 | no-const-assign: 0
204 | no-dupe-class-members: 0
205 | no-this-before-super: 0
206 | no-var: 0
207 | object-shorthand: 0
208 | prefer-arrow-callback: 0
209 | prefer-const: 0
210 | prefer-reflect: 0
211 | prefer-spread: 0
212 | prefer-template: 0
213 | require-yield: 0
214 |
--------------------------------------------------------------------------------
/view/frontend/web/js/geocoder.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | 'uiComponent',
4 | 'uiRegistry',
5 | 'mage/translate'
6 | ], function ($, Component, registry) {
7 | return Component.extend({
8 | defaults: {
9 | provider: "osm",
10 | radius: 25000
11 | },
12 |
13 | /**
14 | * Component Constructor
15 | */
16 | initialize: function () {
17 | this._super();
18 | this.observe(['fulltextSearch', 'currentResult']);
19 | },
20 |
21 | /**
22 | * Init the geocoder component
23 | *
24 | * @param element Element triggering the init
25 | * @param component The JS Component
26 | */
27 | initGeocoder: function (element, component) {
28 | if (component.provider !== null && (typeof component.provider === 'function' || typeof component.provider === 'object')) {
29 | component.provider.init(component, component.onGeocoderReady.bind(component));
30 | } else {
31 | require(['smile-geocoder-provider-' + component.provider], function(provider) {
32 | provider.init(component, component.onGeocoderReady.bind(component));
33 | component.provider = provider;
34 | }).bind(this);
35 | }
36 | },
37 |
38 | /**
39 | * Assign geocoder component after init. Used as a callback.
40 | */
41 | onGeocoderReady: function() {
42 | this.geocoder = this.provider.getGeocoder();
43 | },
44 |
45 | /**
46 | * Trigger the geocoding on search. Exposes current result then.
47 | *
48 | * @param {function} noResultCallback Callback executed if no result was found
49 | */
50 | onSearch: function(noResultCallback = null) {
51 | if (!this.fulltextSearch() || this.fulltextSearch().trim().length === 0) {
52 | this.currentResult(null);
53 | } else {
54 | var geocodingOptions = {};
55 | this.geocoder.geocode(this.fulltextSearch(), geocodingOptions, function (results) {
56 | if (results.length > 0) {
57 | this.currentResult(results[0]);
58 |
59 | return;
60 | }
61 |
62 | if (typeof noResultCallback === 'function') {
63 | noResultCallback();
64 | }
65 | }.bind(this));
66 | }
67 | },
68 |
69 | /**
70 | * Filters a given list of markers being around a position, for the current radius
71 | *
72 | * @param markersList An array containing the markers
73 | * @param centerPosition The center position
74 | * @param radius The radius to check
75 | *
76 | * @returns {*|Array}
77 | */
78 | filterMarkersListByPositionRadius: function(markersList, centerPosition, radius) {
79 | if (!radius) {
80 | radius = parseInt(this.radius, 10);
81 | }
82 | if (this.geocoder == undefined && typeof this.provider === 'object') {
83 | this.geocoder = this.provider.getGeocoder();
84 | }
85 | return this.geocoder.filterMarkersListByPositionRadius(markersList, centerPosition, radius)
86 | },
87 |
88 | /**
89 | * Geolocalize current user
90 | * Uses navigator.geolocation object to retrieve position.
91 | *
92 | * Fallbacks to a localization by API if
93 | * @param callback
94 | */
95 | geolocalize: function(callback) {
96 | if (navigator.geolocation) {
97 | navigator.geolocation.getCurrentPosition(
98 | callback,
99 | function(error) {
100 | if (error.message.indexOf("Only secure origins are allowed") === 0
101 | || error.code === error.POSITION_UNAVAILABLE
102 | || error.code === error.TIMEOUT) {
103 | this.geoLocalizeViaApi(callback);
104 | }
105 | }.bind(this),
106 | {maximumAge: 50000, timeout: 20000, enableHighAccuracy: true}
107 | );
108 | }
109 | },
110 |
111 | /**
112 | * Geolocalizes the user via the geocoder provider.
113 | * Called as a fallback when geolocation through user browser fails.
114 | *
115 | * @param callback
116 | * @returns {*}
117 | */
118 | geoLocalizeViaApi: function(callback) {
119 | return this.geocoder.geoLocalizeViaApi(callback);
120 | },
121 |
122 | /**
123 | * Get address data by lat / lng
124 | *
125 | * @param {Number} latitude
126 | * @param {Number} longitude
127 | * @param {Function} callback
128 | *
129 | * @return {jqXHR}
130 | */
131 | getAddressByLatLng: function (latitude, longitude, callback) {
132 | return this.geocoder.getAddressByLatLng(latitude, longitude, callback);
133 | }
134 | });
135 | });
136 |
--------------------------------------------------------------------------------
/etc/adminhtml/system.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | separator-top
7 |
8 | service
9 | Magento_Backend::smile_map
10 |
11 |
12 |
13 |
14 |
15 |
16 | Smile\Map\Model\Config\Source\MapProvider
17 |
18 |
19 |
20 |
21 |
22 | google
23 |
24 |
25 |
26 |
27 |
28 | Get your api key : https://ipstack.com/product
29 |
30 | osm
31 |
32 |
33 |
34 |
35 |
36 |
37 | google
38 |
39 | Comma separated list of Google libraries to use. They must be enabled for your API Key. Eg: geometry, places
40 |
41 |
42 |
43 |
44 |
45 | google
46 |
47 | Google Mapstyles]]>
48 |
49 |
50 |
51 |
52 |
53 | osm
54 |
55 |
56 |
57 |
58 |
59 |
60 | google
61 |
62 |
63 |
64 |
65 |
66 | Smile\Map\Model\Config\Backend\MarkerIcon
67 | smile_map/marker
68 | smile_map/marker
69 |
70 |
71 |
72 |
73 | Magento\Config\Model\Config\Source\Yesno
74 |
75 |
76 |
77 |
78 | Magento\Config\Model\Config\Source\Yesno
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/view/frontend/web/js/geocoder-provider/osm.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | 'leaflet',
4 | 'geoAddressModel'
5 | ], function ($, L, GeoAddressModel) {
6 |
7 | const BASE_API_URL = '//nominatim.openstreetmap.org';
8 |
9 | function getServiceUrl(qry) {
10 | var parameters = L.Util.extend({
11 | q: qry,
12 | format: 'json'
13 | }, this.options);
14 |
15 | return 'https:'
16 | + '//nominatim.openstreetmap.org/search'
17 | + L.Util.getParamString(parameters);
18 | }
19 |
20 | function getGeolocalizeApi(config) {
21 | return '//api.ipstack.com/check?output=json&fields=latitude,longitude&access_key='+ config['api_key'];
22 | }
23 |
24 | /**
25 | * Geocoder constructor
26 | *
27 | * @param options Options configuration
28 | * @constructor
29 | */
30 | function Geocoder(options) {
31 | this.options = options;
32 | }
33 |
34 | /**
35 | * Geocode a text query (address, postcode, etc...)
36 | *
37 | * @param queryText The query text
38 | * @param options The geocoder options
39 | * @param callback potential callback to call on results
40 | */
41 | Geocoder.prototype.geocode = function (queryText, options, callback) {
42 | var queryUrl = getServiceUrl(queryText);
43 | $.getJSON(queryUrl, function(results) {
44 | results = results.map(this.prepareResult);
45 | callback(results);
46 | }.bind(this));
47 | };
48 |
49 | /**
50 | * Parse results before returning them
51 | *
52 | * @param result
53 | * @returns {{name: *, bounds: o.LatLngBounds, location: o.LatLng}}
54 | */
55 | Geocoder.prototype.prepareResult = function (result) {
56 |
57 | var boundingBox = result.boundingbox,
58 | northEastLatLng = new L.LatLng( boundingBox[1], boundingBox[3] ),
59 | southWestLatLng = new L.LatLng( boundingBox[0], boundingBox[2] );
60 |
61 | var processedResult = {
62 | name : result.display_name,
63 | bounds : new L.LatLngBounds([
64 | northEastLatLng,
65 | southWestLatLng
66 | ]),
67 | location: new L.LatLng(result.lat,result.lon)
68 | };
69 |
70 | return processedResult;
71 | };
72 |
73 | /**
74 | * Filter a markerlist to return only those being on a radius around a given position.
75 | *
76 | * @param markersList The marker lists
77 | * @param centerPosition The center position
78 | * @param radius The radius to check, in meters
79 | *
80 | * @returns {Array}
81 | */
82 | Geocoder.prototype.filterMarkersListByPositionRadius = function (markersList, centerPosition, radius) {
83 |
84 | var center = new L.LatLng(centerPosition.lat, centerPosition.lng);
85 | var list = [];
86 |
87 | markersList.forEach(function(marker) {
88 | var itemPosition = new L.LatLng(marker.latitude, marker.longitude);
89 | var distance = itemPosition.distanceTo(center);
90 | if (distance <= radius) {
91 | marker.distance(distance);
92 | list.push(marker);
93 | }
94 | }, this);
95 |
96 | return list;
97 | };
98 |
99 | /**
100 | * Retrieve User Geolocalization. Used as a fallback when navigator.geolocation.getCurrentPosition fails
101 | *
102 | * @param callback
103 | */
104 | Geocoder.prototype.geoLocalizeViaApi = function (callback) {
105 | $.getJSON(getGeolocalizeApi(this.options), function(success) {callback({coords: {latitude: success.latitude, longitude: success.longitude}})});
106 | };
107 |
108 | /**
109 | * Retrieve address data by latitude / longitude
110 | *
111 | * @param {Number} latitude
112 | * @param {Number} longitude
113 | * @param {Function} callback
114 | *
115 | * @return {jqXHR}
116 | */
117 | Geocoder.prototype.getAddressByLatLng = function (latitude, longitude, callback) {
118 | return $.getJSON(BASE_API_URL + '/reverse?format=json&lat=' + latitude + '&lon=' + longitude, function (resp) {
119 | if (resp.hasOwnProperty('error') || !resp.hasOwnProperty('address') || !resp.hasOwnProperty('lat') ||
120 | !resp.hasOwnProperty('lon')
121 | ) {
122 | callback({successResponse: false});
123 |
124 | return;
125 | }
126 |
127 | let address = resp.address;
128 | let city = address.hasOwnProperty('city') ?
129 | address.city : (address.hasOwnProperty('village') ? address.village : '');
130 |
131 | callback({
132 | successResponse: true,
133 | address: new GeoAddressModel({
134 | countryCode: address.hasOwnProperty('country_code') ? address.country_code : '',
135 | country: address.hasOwnProperty('country') ? address.country : '',
136 | city: city,
137 | postCode: address.hasOwnProperty('postcode') ? address.postcode : '',
138 | street: address.hasOwnProperty('road') ? address.road : '',
139 | streetNumber: address.hasOwnProperty('house_number') ? address.house_number : '',
140 | position: {
141 | latitude: resp.hasOwnProperty('lat') ? resp.lat : '',
142 | longitude: resp.hasOwnProperty('lon') ? resp.lon : ''
143 | }
144 | })
145 | });
146 | }).fail(function () {
147 | callback({successResponse: false});
148 | });
149 | };
150 |
151 | /**
152 | * Immutable provider for Geocoder retrieval.
153 | *
154 | * @type {{init: Provider.init, getGeocoder: Provider.getGeocoder}}
155 | */
156 | Provider = {
157 | init: function(config, callback) {
158 | require(['leaflet-geosearch-osm'], function() {
159 | Provider.geocoder = new Geocoder(config);
160 | if (callback !== undefined) {
161 | return callback();
162 | }
163 | });
164 | },
165 |
166 | getGeocoder: function() {
167 | return Provider.geocoder;
168 | }
169 | };
170 |
171 | return Provider;
172 | });
173 |
--------------------------------------------------------------------------------
/view/frontend/web/js/geocoder-provider/google.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | 'leaflet',
4 | 'underscore',
5 | 'geoAddressModel'
6 | ], function ($, L, _, GeoAddressModel) {
7 |
8 | const BASE_API_URL = '//maps.google.com/maps/api';
9 |
10 | /**
11 | * Retrieve Google Maps API
12 | *
13 | * @param config the config (contains API Key, country, etc...).
14 | *
15 | * @returns {string}
16 | */
17 | function getApiUrl(config) {
18 | var apiKey = config['api_key'];
19 | var country = config['country'] || 'FR';
20 | var locale = config['locale'] || 'fr_FR';
21 | var libraries = config['libraries'] || 'geometry';
22 |
23 | return BASE_API_URL + '/js?key=' + apiKey + '&libraries=' + libraries + '&language=' + locale + '&country=' + country;
24 | }
25 |
26 | /**
27 | * Retrieve Google Geolocalization API URL
28 | *
29 | * @param config the config (contains API Key, country, etc...).
30 | *
31 | * @returns {string}
32 | */
33 | function getGeolocalizeApi(config) {
34 | var apiKey = config['api_key'];
35 | return 'https://www.googleapis.com/geolocation/v1/geolocate?key=' + apiKey;
36 | }
37 |
38 | var geocoder = null;
39 |
40 | /**
41 | * Geocoder constructor
42 | *
43 | * @param options Options configuration
44 | * @constructor
45 | */
46 | function Geocoder(options) {
47 | this.options = options;
48 | }
49 |
50 | /**
51 | * Geocode a text query (address, postcode, etc...)
52 | *
53 | * @param queryText The query text
54 | * @param options The geocoder options
55 | * @param callback potential callback to call on results
56 | */
57 | Geocoder.prototype.geocode = function (queryText, options, callback) {
58 | if (geocoder === null && google && google.maps) {
59 | geocoder = new google.maps.Geocoder();
60 | } else if (geocoder === null) {
61 | throw __('Google Maps API is not ready yet.')
62 | }
63 |
64 | var request = {address: queryText, region: 'FR'};
65 |
66 | geocoder.geocode(request, function(results) {
67 | results = results.map(this.prepareResult);
68 |
69 | if (options['bounds']) {
70 | results = results.filter(function(result) { return options['bounds'].contains(result.location); });
71 | }
72 |
73 | callback(results);
74 | }.bind(this));
75 | };
76 |
77 | /**
78 | * Parse results before returning them
79 | *
80 | * @param result
81 | * @returns {{name: *, bounds: o.LatLngBounds, location: o.LatLng}}
82 | */
83 | Geocoder.prototype.prepareResult = function (result) {
84 | var processedResult = {};
85 |
86 | if (result['geometry']['bounds']) {
87 | processedResult = {
88 | name: result['address_components'][0]['short_name'],
89 | bounds: new L.LatLngBounds(
90 | {
91 | lat: result['geometry']['bounds'].getNorthEast().lat(),
92 | lng: result['geometry']['bounds'].getNorthEast().lng()
93 | },
94 | {
95 | lat: result['geometry']['bounds'].getSouthWest().lat(),
96 | lng: result['geometry']['bounds'].getSouthWest().lng()
97 | }
98 | ),
99 | location: new L.LatLng(result['geometry']['location'].lat(), result['geometry']['location'].lng())
100 | };
101 | }
102 |
103 | return processedResult;
104 | };
105 |
106 | /**
107 | * Filter a markerlist to return only those being on a radius around a given position.
108 | *
109 | * @param markersList The marker lists
110 | * @param centerPosition The center position
111 | * @param radius The radius to check, in meters
112 | *
113 | * @returns {Array}
114 | */
115 | Geocoder.prototype.filterMarkersListByPositionRadius = function (markersList, centerPosition, radius) {
116 |
117 | var center = new google.maps.LatLng(centerPosition.lat, centerPosition.lng);
118 | var list = [];
119 |
120 | markersList.forEach(function(marker) {
121 | var itemPosition = new google.maps.LatLng(marker.latitude, marker.longitude);
122 | var distance = google.maps.geometry.spherical.computeDistanceBetween(itemPosition, center);
123 | if (distance <= radius) {
124 | marker.distance(distance);
125 | list.push(marker);
126 | }
127 | }, this);
128 |
129 | return list;
130 | };
131 |
132 | /**
133 | * Retrieve User Geolocalization. Used as a fallback when navigator.geolocation.getCurrentPosition fails
134 | *
135 | * @param callback
136 | */
137 | Geocoder.prototype.geoLocalizeViaApi = function (callback) {
138 | $.post(getGeolocalizeApi(this.options), function(success) {callback({coords: {latitude: success.location.lat, longitude: success.location.lng}})});
139 | };
140 |
141 | /**
142 | * Retrieve address data by latitude / longitude
143 | *
144 | * @param {Number} latitude
145 | * @param {Number} longitude
146 | * @param {Function} callback
147 | *
148 | * @return {jqXHR}
149 | */
150 | Geocoder.prototype.getAddressByLatLng = function (latitude, longitude, callback) {
151 | let url = BASE_API_URL + '/geocode/json?latlng=' + latitude + ',' + longitude +
152 | '&key=' + this.options['api_key'];
153 |
154 | return $.getJSON(url, function (resp) {
155 | if (!resp.hasOwnProperty('status') || resp.status !== 'OK' || !resp.hasOwnProperty('results')) {
156 | callback({successResponse: false});
157 |
158 | return;
159 | }
160 |
161 | callback({
162 | successResponse: true,
163 | address: this._buildAddressModel(resp.results)
164 | });
165 | }.bind(this)).fail(function () {
166 | callback({successResponse: false});
167 | });
168 | };
169 |
170 | /**
171 | * Build address model
172 | *
173 | * @param {Object} geoResult
174 | * @returns {geoAddressModel}
175 | * @private
176 | */
177 | Geocoder.prototype._buildAddressModel = function (geoResult) {
178 | let addressData = {};
179 |
180 | if (geoResult.hasOwnProperty('address_components')) {
181 | let addressMapping = {
182 | streetNumber: 'street_number',
183 | country: 'country',
184 | countryCode: 'country',
185 | city: 'locality',
186 | postCode: 'postal_code',
187 | street: 'route'
188 | };
189 |
190 | _.each(geoResult.address_components, function (addressComponent) {
191 | if (addressComponent.hasOwnProperty('types')) {
192 | let attribute = null;
193 | _.every(addressMapping, function (geoAttribute, mappedAttribute) {
194 | if (_.contains(addressComponent.types, geoAttribute)) {
195 | attribute = mappedAttribute;
196 |
197 | return false;
198 | }
199 |
200 | return true;
201 | });
202 |
203 | if (!_.isNull(attribute)) {
204 | addressData[attribute] = attribute === 'countryCode' ?
205 | addressComponent['short_name'] :
206 | addressComponent['long_name'];
207 | }
208 | }
209 | });
210 | }
211 |
212 | if (geoResult.hasOwnProperty('geometry') && geoResult.geometry.hasOwnProperty('location')) {
213 | addressData.position.latitude = geoResult.geometry.location.lat;
214 | addressData.position.longitude = geoResult.geometry.location.lng;
215 | }
216 |
217 | return new GeoAddressModel(addressData);
218 | };
219 |
220 | /**
221 | * Immutable provider for Geocoder retrieval.
222 | *
223 | * @type {{init: Provider.init, getGeocoder: Provider.getGeocoder}}
224 | */
225 | Provider = {
226 | init: function(config, callback) {
227 | require([getApiUrl(config)], function() {
228 | Provider.geocoder = new Geocoder(config);
229 | if (callback !== undefined) {
230 | return callback();
231 | }
232 | });
233 | },
234 |
235 | getGeocoder: function() {
236 | return Provider.geocoder;
237 | }
238 | };
239 |
240 | return Provider;
241 | });
242 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/plugins/geosearch/l.control.geosearch.js:
--------------------------------------------------------------------------------
1 | /*
2 | * L.Control.GeoSearch - search for an address and zoom to its location
3 | * https://github.com/smeijer/L.GeoSearch
4 | */
5 |
6 | L.GeoSearch = {};
7 | L.GeoSearch.Provider = {};
8 |
9 | L.GeoSearch.Result = function (x, y, label, bounds, details) {
10 | this.X = x;
11 | this.Y = y;
12 | this.Label = label;
13 | this.bounds = bounds;
14 |
15 | if (details)
16 | this.details = details;
17 | };
18 |
19 | L.Control.GeoSearch = L.Control.extend({
20 | options: {
21 | position: 'topleft',
22 | showMarker: true,
23 | showPopup: false,
24 | customIcon: false,
25 | retainZoomLevel: false,
26 | draggable: false
27 | },
28 |
29 | _config: {
30 | country: '',
31 | searchLabel: 'Enter address',
32 | notFoundMessage: 'Sorry, that address could not be found.',
33 | messageHideDelay: 3000,
34 | zoomLevel: 18
35 | },
36 |
37 | initialize: function (options) {
38 | L.Util.extend(this.options, options);
39 | L.Util.extend(this._config, options);
40 | },
41 |
42 | resetLink: function(extraClass) {
43 | var link = this._container.querySelector('a');
44 | link.className = 'leaflet-bar-part leaflet-bar-part-single' + ' ' + extraClass;
45 | },
46 |
47 | onAdd: function (map) {
48 | this._container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-geosearch');
49 |
50 | // create the link - this will contain one of the icons
51 | var link = L.DomUtil.create('a', '', this._container);
52 | link.href = '#';
53 | link.title = this._config.searchLabel;
54 |
55 | // set the link's icon to magnifying glass
56 | this.resetLink('glass');
57 |
58 | // create the form that will contain the input
59 | var form = L.DomUtil.create('form', 'displayNone', this._container);
60 |
61 | // create the input, and set its placeholder text
62 | var searchbox = L.DomUtil.create('input', null, form);
63 | searchbox.type = 'text';
64 | searchbox.placeholder = this._config.searchLabel;
65 | this._searchbox = searchbox;
66 |
67 | var msgbox = L.DomUtil.create('div', 'leaflet-bar message displayNone', this._container);
68 | this._msgbox = msgbox;
69 |
70 | L.DomEvent
71 | .on(link, 'click', L.DomEvent.stopPropagation)
72 | .on(link, 'click', L.DomEvent.preventDefault)
73 | .on(link, 'click', function() {
74 |
75 | if (L.DomUtil.hasClass(form, 'displayNone')) {
76 | L.DomUtil.removeClass(form, 'displayNone'); // unhide form
77 | searchbox.focus();
78 | } else {
79 | L.DomUtil.addClass(form, 'displayNone'); // hide form
80 | }
81 |
82 | })
83 | .on(link, 'dblclick', L.DomEvent.stopPropagation);
84 |
85 | L.DomEvent
86 | .addListener(this._searchbox, 'keypress', this._onKeyPress, this)
87 | .addListener(this._searchbox, 'keyup', this._onKeyUp, this)
88 | .addListener(this._searchbox, 'input', this._onInput, this);
89 |
90 | L.DomEvent.disableClickPropagation(this._container);
91 |
92 | return this._container;
93 | },
94 |
95 | geosearch: function (qry) {
96 | var that = this;
97 | try {
98 | var provider = this._config.provider;
99 |
100 | if(typeof provider.GetLocations == 'function') {
101 | provider.GetLocations(qry, function(results) {
102 | that._processResults(results, qry);
103 | });
104 | }
105 | else {
106 | var url = provider.GetServiceUrl(qry);
107 | this.sendRequest(provider, url, qry);
108 | }
109 | }
110 | catch (error) {
111 | this._printError(error);
112 | }
113 | },
114 |
115 | cancelSearch: function() {
116 | var form = this._container.querySelector('form');
117 | L.DomUtil.addClass(form, 'displayNone');
118 |
119 | this._searchbox.value = '';
120 | this.resetLink('glass');
121 |
122 | L.DomUtil.addClass(this._msgbox, 'displayNone');
123 |
124 | this._map._container.focus();
125 | },
126 |
127 | startSearch: function() {
128 | // show spinner icon
129 | this.resetLink('spinner');
130 | this.geosearch(this._searchbox.value);
131 | },
132 |
133 | sendRequest: function (provider, url, qry) {
134 | var that = this;
135 |
136 | window.parseLocation = function (response) {
137 | var results = provider.ParseJSON(response);
138 | that._processResults(results, qry);
139 |
140 | document.body.removeChild(document.getElementById('getJsonP'));
141 | delete window.parseLocation;
142 | };
143 |
144 | function getJsonP (url) {
145 | url = url + '&callback=parseLocation';
146 | var script = document.createElement('script');
147 | script.id = 'getJsonP';
148 | script.src = url;
149 | script.async = true;
150 | document.body.appendChild(script);
151 | }
152 |
153 | if (XMLHttpRequest) {
154 | var xhr = new XMLHttpRequest();
155 |
156 | if ('withCredentials' in xhr) {
157 | var xhr = new XMLHttpRequest();
158 |
159 | xhr.onreadystatechange = function () {
160 | if (xhr.readyState == 4) {
161 | if (xhr.status == 200) {
162 | var response = JSON.parse(xhr.responseText),
163 | results = provider.ParseJSON(response);
164 |
165 | that._processResults(results, qry);
166 | } else if (xhr.status == 0 || xhr.status == 400) {
167 | getJsonP(url);
168 | } else {
169 | that._printError(xhr.responseText);
170 | }
171 | }
172 | };
173 |
174 | xhr.open('GET', url, true);
175 | xhr.send();
176 | } else if (XDomainRequest) {
177 | var xdr = new XDomainRequest();
178 |
179 | xdr.onerror = function (err) {
180 | that._printError(err);
181 | };
182 |
183 | xdr.onload = function () {
184 | var response = JSON.parse(xdr.responseText),
185 | results = provider.ParseJSON(response);
186 |
187 | that._processResults(results, qry);
188 | };
189 |
190 | xdr.open('GET', url);
191 | xdr.send();
192 | } else {
193 | getJsonP(url);
194 | }
195 | }
196 | },
197 |
198 | _processResults: function(results, qry) {
199 | if (results.length > 0) {
200 | this._map.fireEvent('geosearch_foundlocations', {Locations: results});
201 | this._showLocation(results[0], qry);
202 | this.cancelSearch();
203 | } else {
204 | this._printError(this._config.notFoundMessage);
205 | }
206 | },
207 |
208 | _showLocation: function (location, qry) {
209 | if (this.options.showMarker == true) {
210 | if (typeof this._positionMarker === 'undefined') {
211 | this._positionMarker = L.marker(
212 | [location.Y, location.X],
213 | {draggable: this.options.draggable}
214 | ).addTo(this._map);
215 | if( this.options.customIcon ) {
216 | this._positionMarker.setIcon(this.options.customIcon);
217 | }
218 | if( this.options.showPopup ) {
219 | this._positionMarker.bindPopup(qry).openPopup();
220 | }
221 | }
222 | else {
223 | this._positionMarker.setLatLng([location.Y, location.X]);
224 | if( this.options.showPopup ) {
225 | this._positionMarker.bindPopup(qry).openPopup();
226 | }
227 | }
228 | }
229 | if (!this.options.retainZoomLevel && location.bounds && location.bounds.isValid()) {
230 | this._map.fitBounds(location.bounds);
231 | }
232 | else {
233 | this._map.setView([location.Y, location.X], this._getZoomLevel(), false);
234 | }
235 |
236 | this._map.fireEvent('geosearch_showlocation', {
237 | Location: location,
238 | Marker : this._positionMarker
239 | });
240 | },
241 |
242 | _isShowingError: false,
243 |
244 | _printError: function(message) {
245 | this._msgbox.innerHTML = message;
246 | L.DomUtil.removeClass(this._msgbox, 'displayNone');
247 |
248 | this._map.fireEvent('geosearch_error', {message: message});
249 |
250 | // show alert icon
251 | this.resetLink('alert');
252 | this._isShowingError = true;
253 | },
254 |
255 | _onKeyUp: function (e) {
256 | var esc = 27;
257 |
258 | if (e.keyCode === esc) { // escape key detection is unreliable
259 | this.cancelSearch();
260 | }
261 | },
262 |
263 | _getZoomLevel: function() {
264 | if (! this.options.retainZoomLevel) {
265 | return this._config.zoomLevel;
266 | }
267 | return this._map._zoom;
268 | },
269 |
270 | _onInput: function() {
271 | if (this._isShowingError) {
272 | this.resetLink('glass');
273 | L.DomUtil.addClass(this._msgbox, 'displayNone');
274 |
275 | this._isShowingError = false;
276 | }
277 | },
278 |
279 | _onKeyPress: function (e) {
280 | var enterKey = 13;
281 |
282 | if (e.keyCode === enterKey) {
283 | e.preventDefault();
284 | e.stopPropagation();
285 |
286 | this.startSearch();
287 | }
288 | }
289 | });
290 |
--------------------------------------------------------------------------------
/view/frontend/web/js/map.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | 'uiComponent',
4 | 'leaflet',
5 | 'ko',
6 | 'uiRegistry',
7 | 'smile-map-markers',
8 | 'mage/translate',
9 | 'leaflet-markercluster'
10 | ], function ($, Component, L, ko, registry, MarkersList) {
11 | return Component.extend({
12 | defaults: {
13 | provider : "osm",
14 | tile_url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
15 | controls_position: 'topright',
16 | markers : [],
17 | markerIconSize: [18,23],
18 | selectedMarker : null
19 | },
20 |
21 | /**
22 | * Map constructor
23 | */
24 | initialize: function () {
25 | this._super();
26 | this.initMarkers();
27 | this.observeElements();
28 | },
29 |
30 | /**
31 | * Init markers on the map
32 | */
33 | initMarkers: function() {
34 | var markersList = new MarkersList({items : this.markers});
35 | this.markers = markersList.getList();
36 |
37 | this.markers.forEach(function(marker) {
38 | marker.distance = ko.observable(0);
39 | });
40 |
41 | this.displayedMarkers = ko.observable(this.markers);
42 | },
43 |
44 | /**
45 | * Observe events on elements
46 | */
47 | observeElements: function() {
48 | this.observe(['markers', 'displayedMarkers', 'nearbyMarkers', 'selectedMarker', 'fulltextSearch']);
49 | this.markers.subscribe(this.loadMarkers.bind(this));
50 | },
51 |
52 | /**
53 | * Init the Map. Called as callback after component initialization
54 | *
55 | * @param element Map element
56 | * @param component Component
57 | */
58 | initMap: function (element, component) {
59 | component.map = L.map(element, {zoomControl: false, attributionControl: true, scrollWheelZoom: $(document).width() > 480});
60 | component.map.on('moveend', component.refreshDisplayedMarkers.bind(component));
61 |
62 | layerControl = L.control.zoom({position: component['controls_position']});
63 | layerControl.addTo(component.map);
64 |
65 | if (component.provider !== null && (typeof component.provider === 'function' || typeof component.provider === 'object')) {
66 | component.provider.init(component.map, component, component.onMapReady.bind(component));
67 | } else {
68 | require(['smile-map-provider-' + component.provider], function (provider) {
69 | provider.init(component.map, component, component.onMapReady.bind(component));
70 | component.provider = provider;
71 | });
72 | }
73 | },
74 |
75 | /**
76 | * Reset the map
77 | */
78 | resetMap: function() {
79 | this.selectedMarker(null);
80 | this.currentBounds = this.initialBounds;
81 | this.map.fitBounds(this.initialBounds);
82 | this.resetHash();
83 | },
84 |
85 | /**
86 | * Reset the bounds : usefull to zoom out the map after having zoomed on a dedicated marker
87 | */
88 | resetBounds: function() {
89 | this.selectedMarker(null);
90 | this.map.fitBounds(this.currentBounds);
91 | var center = this.currentBounds.getCenter();
92 | this.setHashFromLocation({coords : {latitude : center.lat, longitude : center.lng}});
93 | },
94 |
95 | /**
96 | * Callback after map provider is ready and has been initialized
97 | */
98 | onMapReady: function() {
99 | this.initGeocoderBinding();
100 | this.loadMarkers();
101 | this.initPosition();
102 | },
103 |
104 | /**
105 | * Init current position from URL query params, if any.
106 | */
107 | initPosition: function () {
108 | var position = this.getLocationFromHash();
109 | if (position !== null) {
110 | this.currentBounds = this.initialBounds;
111 | this.applyPosition(position);
112 | } else {
113 | this.map.fitBounds(this.initialBounds);
114 | }
115 | },
116 |
117 | /**
118 | * Center the map on a given position
119 | *
120 | * @param position
121 | */
122 | applyPosition: function(position) {
123 | if (position && position.coords) {
124 | var coords = new L.latLng(position.coords.latitude, position.coords.longitude);
125 |
126 | var isMarker = false;
127 | this.markers().forEach(function(marker) {
128 | if (marker.latitude === position.coords.latitude && marker.longitude === position.coords.longitude) {
129 | isMarker = marker;
130 | }
131 | }, this);
132 |
133 | if (isMarker) {
134 | this.currentBounds = this.initialBounds;
135 | this.selectedMarker(isMarker);
136 | this.refreshNearByMarkers(new L.latLng(isMarker.latitude, isMarker.longitude), true);
137 | this.map.setView(coords, 15);
138 | } else {
139 | this.map.setView(coords, 11);
140 | this.currentBounds = this.map.getBounds();
141 | }
142 |
143 | this.setHashFromLocation(position);
144 | }
145 | },
146 |
147 | /**
148 | * Geolocalize the user with geocoder and apply position to map.
149 | */
150 | geolocalize: function() {
151 | if (this.geocoder) {
152 | this.geocoder.geolocalize(this.applyPosition.bind(this));
153 | }
154 | },
155 |
156 | /**
157 | * Init the geocoding component binding
158 | */
159 | initGeocoderBinding: function() {
160 | registry.get(this.name + '.geocoder', function (geocoder) {
161 | this.geocoder = geocoder;
162 |
163 | geocoder.currentResult.subscribe(this.currentResultSubscribed.bind(this));
164 | }.bind(this));
165 |
166 | // geolocalize after url redirect : we bet that according to url parts, the user has clicked on geolocalize
167 | if (navigator.geolocation && window.location.search === '' && window.location.hash.length > 1) {
168 | this.geolocalize();
169 | }
170 | },
171 |
172 | /**
173 | * Load the markers and centers the map on them.
174 | */
175 | loadMarkers: function() {
176 | var markers = [],
177 | isMarkerCluster = this.marker_cluster === '1';
178 | var icon = L.icon({iconUrl: this.markerIcon, iconSize: this.markerIconSize});
179 | this.markers().forEach(function(markerData) {
180 | var currentMarker = [markerData.latitude, markerData.longitude];
181 | var marker = L.marker(currentMarker, {icon: icon});
182 | if (!isMarkerCluster) {
183 | marker.addTo(this.map);
184 | }
185 | marker.on('click', function() {
186 | this.selectMarker(markerData);
187 | }.bind(this));
188 | markers.push(marker);
189 | }.bind(this));
190 |
191 | var group = new L.featureGroup(markers);
192 | if (isMarkerCluster) {
193 | group = new L.markerClusterGroup();
194 | group.addLayers(markers);
195 | this.map.addLayer(group);
196 | }
197 | this.initialBounds = group.getBounds();
198 | },
199 |
200 | /**
201 | * Reset the currently selected marker
202 | */
203 | resetSelectedMarker: function () {
204 | this.selectedMarker(null);
205 | },
206 |
207 | /**
208 | * Select a given marker
209 | *
210 | * @param marker
211 | */
212 | selectMarker: function(marker) {
213 | // Set current bounds before zooming in : to allow returning to these bounds after.
214 | if (!this.selectedMarker()) {
215 | this.currentBounds = this.map.getBounds();
216 | }
217 |
218 | this.selectedMarker(marker);
219 | var coords = new L.latLng(marker.latitude, marker.longitude);
220 | this.refreshNearByMarkers(coords);
221 | this.setHashFromLocation({coords : marker});
222 |
223 | this.map.setView(coords, 15);
224 | },
225 |
226 | /**
227 | * Retrieve a list of markers nearby given coordinates
228 | *
229 | * @param coords
230 | * @param removeFirstMarker
231 | */
232 | refreshNearByMarkers: function(coords, removeFirstMarker) {
233 | removeFirstMarker = typeof removeFirstMarker === 'undefined' ? false : removeFirstMarker;
234 | if (this.geocoder) {
235 | var nearbyMarkers = this.geocoder.filterMarkersListByPositionRadius(this.markers(), coords);
236 | nearbyMarkers = nearbyMarkers.sort(function(a, b) {
237 | var distanceA = ko.isObservable(a['distance']) ? a['distance']() : a['distance'],
238 | distanceB = ko.isObservable(b['distance']) ? b['distance']() : b['distance'];
239 | return ((distanceA < distanceB) ? - 1 : ((distanceA > distanceB) ? 1 : 0));
240 | });
241 |
242 | if (removeFirstMarker) {
243 | nearbyMarkers.shift();
244 | }
245 | this.nearbyMarkers(nearbyMarkers);
246 | }
247 | },
248 |
249 | /**
250 | * Refresh markers according to current bounds.
251 | */
252 | refreshDisplayedMarkers: function () {
253 | var bounds = this.map.getBounds();
254 | var displayedMarkers = this.filterMarkersByBounds(this.markers(), bounds);
255 |
256 | var zoom = this.map.getZoom();
257 |
258 | if (displayedMarkers.length === 0 && this.disabled_zoom_out !== '1') {
259 | zoom = zoom - 1;
260 | this.map.setZoom(zoom);
261 | }
262 |
263 | displayedMarkers = this.addDistanceToMarkers(displayedMarkers, this.map.getCenter());
264 |
265 | this.displayedMarkers(displayedMarkers);
266 | },
267 |
268 | /**
269 | * Filters a given list of marker by bounds. Only keep the markers being contained by bounds.
270 | *
271 | * @param markers list of markers
272 | * @param bounds bounds to apply
273 | *
274 | * @returns {Array}
275 | */
276 | filterMarkersByBounds: function (markers, bounds) {
277 |
278 | var list = [];
279 |
280 | markers.forEach(function(marker) {
281 | var coords = new L.latLng(marker.latitude, marker.longitude);
282 | if (bounds.contains(coords)) {
283 |
284 | list.push(marker);
285 | }
286 | }, this);
287 |
288 | return list;
289 | },
290 |
291 | /**
292 | * Count the number of currently displayed markers.
293 | *
294 | * @returns int
295 | */
296 | countDisplayedMarkers : function() {
297 | return this.displayedMarkers().length;
298 | },
299 |
300 | /**
301 | * Count the number of nearby markers.
302 | *
303 | * @returns int
304 | */
305 | countNearbyMarkers : function() {
306 | return this.nearbyMarkers().length;
307 | },
308 |
309 | /**
310 | * If a Reset link can be displayed
311 | *
312 | * @returns {boolean}
313 | */
314 | displayReset : function() {
315 | return this.displayedMarkers().length !== this.markers().length
316 | },
317 |
318 | /**
319 | * Parse query params from query String
320 | *
321 | * @param qs
322 | * @returns {{}}
323 | */
324 | getQueryParams : function (qs) {
325 | qs = qs.split('+').join(' ');
326 |
327 | var params = {},
328 | tokens,
329 | re = /[?&]?([^=]+)=([^&]*)/g;
330 |
331 | while (tokens = re.exec(qs)) {
332 | params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
333 | }
334 |
335 | return params;
336 | },
337 |
338 | /**
339 | * Return current location from URL anchor if any.
340 | *
341 | * @returns {*}
342 | */
343 | getLocationFromHash : function() {
344 | var location = null;
345 |
346 | var hash = window.location.hash.substr(1);
347 | if (hash) {
348 | hash = hash.split(",");
349 | if (hash.length === 2) {
350 | location = {coords: {latitude: hash[0], longitude: hash[1]}};
351 | }
352 | }
353 |
354 | return location;
355 | },
356 |
357 | /**
358 | * Set current window location from Hash
359 | *
360 | * @param location
361 | */
362 | setHashFromLocation : function (location) {
363 | if (location.coords && location.coords.latitude && location.coords.longitude) {
364 | window.location.hash = [location.coords.latitude, location.coords.longitude].join(",");
365 | }
366 | },
367 |
368 | /**
369 | * Reset current window location hash
370 | */
371 | resetHash : function() {
372 | window.location.hash = "_";
373 | return false;
374 | },
375 |
376 | /**
377 | * Add distance from center of map to a given list of markers
378 | *
379 | * @param markersList
380 | * @param centerPosition
381 | * @returns {*}
382 | */
383 | addDistanceToMarkers: function (markersList, centerPosition) {
384 | if (this.provider !== null && (typeof this.provider === 'function' || typeof this.provider === 'object')) {
385 | return this.provider.addDistanceToMarkers(markersList, centerPosition);
386 | } else {
387 | return markersList;
388 | }
389 | },
390 |
391 | /**
392 | * Executed when currentResult is modify
393 | *
394 | * @param {Object} result
395 | */
396 | currentResultSubscribed: function (result) {
397 | if (result && result.bounds) {
398 | this.map.setView(result.location, 11);
399 | this.setHashFromLocation({coords : {latitude : result.location.lat, longitude : result.location.lng}});
400 | this.currentBounds = result.bounds;
401 | } else {
402 | this.resetMap();
403 | }
404 | }
405 | });
406 | });
407 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/google-mutant.js:
--------------------------------------------------------------------------------
1 |
2 | // Based on https://github.com/shramov/leaflet-plugins
3 | // GridLayer like https://avinmathew.com/leaflet-and-google-maps/ , but using MutationObserver instead of jQuery
4 |
5 |
6 | // 🍂class GridLayer.GoogleMutant
7 | // 🍂extends GridLayer
8 | L.GridLayer.GoogleMutant = L.GridLayer.extend({
9 | includes: L.Mixin.Events,
10 |
11 | options: {
12 | minZoom: 0,
13 | maxZoom: 18,
14 | tileSize: 256,
15 | subdomains: 'abc',
16 | errorTileUrl: '',
17 | attribution: '', // The mutant container will add its own attribution anyways.
18 | opacity: 1,
19 | continuousWorld: false,
20 | noWrap: false,
21 | // 🍂option type: String = 'roadmap'
22 | // Google's map type. Valid values are 'roadmap', 'satellite' or 'terrain'. 'hybrid' is not really supported.
23 | type: 'roadmap',
24 | maxNativeZoom: 21,
25 | styles: ''
26 | },
27 |
28 | initialize: function (options) {
29 | L.GridLayer.prototype.initialize.call(this, options);
30 |
31 | this._ready = !!window.google && !!window.google.maps && !!window.google.maps.Map;
32 |
33 | // L.Util.setOptions(this, options);
34 |
35 | this._GAPIPromise = this._ready ? Promise.resolve(window.google) : new Promise(function (resolve, reject) {
36 | var checkCounter = 0;
37 | var intervalId = null;
38 | intervalId = setInterval(function () {
39 | if (checkCounter >= 10) {
40 | clearInterval(intervalId);
41 | return reject(new Error('window.google not found after 10 attempts'));
42 | }
43 | if (!!window.google && !!window.google.maps && !!window.google.maps.Map) {
44 | clearInterval(intervalId);
45 | return resolve(window.google);
46 | }
47 | checkCounter++;
48 | }, 500);
49 | });
50 |
51 | // Couple data structures indexed by tile key
52 | this._tileCallbacks = {}; // Callbacks for promises for tiles that are expected
53 | this._freshTiles = {}; // Tiles from the mutant which haven't been requested yet
54 |
55 | this._imagesPerTile = (this.options.type === 'hybrid') ? 2 : 1;
56 | this.createTile = (this.options.type === 'hybrid') ? this._createMultiTile : this._createSingleTile;
57 | },
58 |
59 | onAdd: function (map) {
60 | L.GridLayer.prototype.onAdd.call(this, map)
61 | this._initMutantContainer();
62 |
63 | this._GAPIPromise.then(function () {
64 | this._ready = true;
65 | this._map = map;
66 |
67 | this._initMutant();
68 |
69 | map.on('viewreset', this._reset, this);
70 | map.on('move', this._update, this);
71 | map.on('zoomend', this._handleZoomAnim, this);
72 | map.on('resize', this._resize, this);
73 |
74 | //20px instead of 1em to avoid a slight overlap with google's attribution
75 | map._controlCorners.bottomright.style.marginBottom = '20px';
76 |
77 | this._reset();
78 | this._update();
79 | }.bind(this));
80 | },
81 |
82 | onRemove: function (map) {
83 | L.GridLayer.prototype.onRemove.call(this, map);
84 | map._container.removeChild(this._mutantContainer);
85 | this._mutantContainer = undefined;
86 |
87 | map.off('viewreset', this._reset, this);
88 | map.off('move', this._update, this);
89 | map.off('zoomend', this._handleZoomAnim, this);
90 | map.off('resize', this._resize, this);
91 |
92 | map._controlCorners.bottomright.style.marginBottom = '0em';
93 | },
94 |
95 | getAttribution: function () {
96 | return this.options.attribution;
97 | },
98 |
99 | setOpacity: function (opacity) {
100 | this.options.opacity = opacity;
101 | if (opacity < 1) {
102 | L.DomUtil.setOpacity(this._mutantContainer, opacity);
103 | }
104 | },
105 |
106 | setElementSize: function (e, size) {
107 | e.style.width = size.x + 'px';
108 | e.style.height = size.y + 'px';
109 | },
110 |
111 | _initMutantContainer: function () {
112 | if (!this._mutantContainer) {
113 | this._mutantContainer = L.DomUtil.create('div', 'leaflet-google-mutant leaflet-top leaflet-left');
114 | this._mutantContainer.id = '_MutantContainer_' + L.Util.stamp(this._mutantContainer);
115 | // this._mutantContainer.style.zIndex = 'auto';
116 | this._mutantContainer.style.pointerEvents = 'none';
117 |
118 | this._map.getContainer().appendChild(this._mutantContainer);
119 | }
120 |
121 | this.setOpacity(this.options.opacity);
122 | this.setElementSize(this._mutantContainer, this._map.getSize());
123 |
124 | this._attachObserver(this._mutantContainer);
125 | },
126 |
127 | _initMutant: function () {
128 | if (!this._ready || !this._mutantContainer) return;
129 | this._mutantCenter = new google.maps.LatLng(0, 0);
130 |
131 | var map = new google.maps.Map(this._mutantContainer, {
132 | center: this._mutantCenter,
133 | zoom: 0,
134 | tilt: 0,
135 | mapTypeId: this.options.type,
136 | disableDefaultUI: true,
137 | keyboardShortcuts: false,
138 | draggable: false,
139 | disableDoubleClickZoom: true,
140 | scrollwheel: false,
141 | streetViewControl: false,
142 | styles: this.options.styles,
143 | backgroundColor: 'transparent'
144 | });
145 |
146 | this._mutant = map;
147 |
148 | // 🍂event spawned
149 | // Fired when the mutant has been created.
150 | this.fire('spawned', {mapObject: map});
151 | },
152 |
153 | _attachObserver: function _attachObserver(node) {
154 | // console.log('Gonna observe', node);
155 |
156 | var observer = new MutationObserver(this._onMutations.bind(this));
157 |
158 | // pass in the target node, as well as the observer options
159 | observer.observe(this._mutantContainer, { childList: true, subtree: true });
160 | },
161 |
162 | _onMutations: function _onMutations(mutations){
163 | for (var i = 0; i < mutations.length; ++i) {
164 | var mutation = mutations[i];
165 | for (var j = 0; j < mutation.addedNodes.length; ++j) {
166 | var node = mutation.addedNodes[j];
167 |
168 | if (node instanceof HTMLImageElement) {
169 | this._onMutatedImage(node);
170 | } else if (node instanceof HTMLElement){
171 | Array.prototype.forEach.call(node.querySelectorAll('img'), this._onMutatedImage.bind(this));
172 | }
173 | };
174 | };
175 | },
176 |
177 | // Only images which 'src' attrib match this will be considered for moving around.
178 | // Looks like some kind of string-based protobuf, maybe??
179 | // Only the roads (and terrain, and vector-based stuff) match this pattern
180 | _roadRegexp: /!1i(\d+)!2i(\d+)!3i(\d+)!/,
181 |
182 | // On the other hand, raster imagery matches this other pattern
183 | _satRegexp: /x=(\d+)&y=(\d+)&z=(\d+)/,
184 |
185 | // On small viewports, when zooming in/out, a static image is requested
186 | // This will not be moved around, just removed from the DOM.
187 | _staticRegExp: /StaticMapService\.GetMapImage/,
188 |
189 | _onMutatedImage: function _onMutatedImage(imgNode) {
190 | // if (imgNode.src) {
191 | // console.log('caught mutated image: ', imgNode.src);
192 | // }
193 |
194 | var coords;
195 | var match = imgNode.src.match(this._roadRegexp);
196 | var sublayer;
197 |
198 | if (match) {
199 | coords = {
200 | z: match[1],
201 | x: match[2],
202 | y: match[3]
203 | }
204 | if (this._imagesPerTile > 1) { imgNode.style.zIndex = 1; }
205 | sublayer = 1;
206 | } else {
207 | match = imgNode.src.match(this._satRegexp);
208 | if (match) {
209 | coords = {
210 | x: match[1],
211 | y: match[2],
212 | z: match[3]
213 | }
214 | }
215 | // imgNode.style.zIndex = 0;
216 | sublayer = 0;
217 | }
218 |
219 | if (coords) {
220 | var key = this._tileCoordsToKey(coords)
221 | if (this._imagesPerTile > 1) { key += '/' + sublayer; }
222 | if (key in this._tileCallbacks && this._tileCallbacks[key]) {
223 | // console.log('Fullfilling callback ', key);
224 | this._tileCallbacks[key].pop()(imgNode);
225 | if (!this._tileCallbacks[key].length) { delete this._tileCallbacks[key]; }
226 | } else {
227 | // console.log('Caching for later', key);
228 | var parent = imgNode.parentNode;
229 | if (parent) {
230 | parent.removeChild(imgNode);
231 | parent.removeChild = L.Util.falseFn;
232 | // imgNode.parentNode.replaceChild(L.DomUtil.create('img'), imgNode);
233 | }
234 | if (key in this._freshTiles) {
235 | this._freshTiles[key].push(imgNode);
236 | } else {
237 | this._freshTiles[key] = [imgNode];
238 | }
239 | }
240 | } else if (imgNode.src.match(this._staticRegExp)) {
241 | var parent = imgNode.parentNode;
242 | if (parent) {
243 | // Remove the image, but don't store it anywhere.
244 | // Image needs to be replaced instead of removed, as the container
245 | // seems to be reused.
246 | imgNode.parentNode.replaceChild(L.DomUtil.create('img'), imgNode);
247 | }
248 | }
249 | },
250 |
251 | // This will be used as this.createTile for 'roadmap', 'sat', 'terrain'
252 | _createSingleTile: function createTile(coords, done) {
253 | var key = this._tileCoordsToKey(coords);
254 | // console.log('Need:', key);
255 |
256 | if (key in this._freshTiles) {
257 | var tile = this._freshTiles[key].pop();
258 | if (!this._freshTiles[key].length) { delete this._freshTiles[key]; }
259 | L.Util.requestAnimFrame(done);
260 | // console.log('Got ', key, ' from _freshTiles');
261 | return tile;
262 | } else {
263 | var tileContainer = L.DomUtil.create('div');
264 | this._tileCallbacks[key] = this._tileCallbacks[key] || [];
265 | this._tileCallbacks[key].push( (function (c, k) {
266 | return function(imgNode) {
267 | var parent = imgNode.parentNode;
268 | if (parent) {
269 | parent.removeChild(imgNode);
270 | parent.removeChild = L.Util.falseFn;
271 | // imgNode.parentNode.replaceChild(L.DomUtil.create('img'), imgNode);
272 | }
273 | c.appendChild(imgNode);
274 | done();
275 | // console.log('Sent ', key, ' to _tileCallbacks');
276 | }.bind(this)
277 | }.bind(this))(tileContainer, key) );
278 |
279 | return tileContainer;
280 | }
281 | },
282 |
283 | // This will be used as this.createTile for 'hybrid'
284 | _createMultiTile: function createTile(coords, done) {
285 | var key = this._tileCoordsToKey(coords);
286 |
287 | var tileContainer = L.DomUtil.create('div');
288 | tileContainer.dataset.pending = this._imagesPerTile;
289 |
290 | for (var i = 0; i < this._imagesPerTile; i++) {
291 | var key2 = key + '/' + i;
292 | if (key2 in this._freshTiles) {
293 | tileContainer.appendChild(this._freshTiles[key2].pop());
294 | if (!this._freshTiles[key2].length) { delete this._freshTiles[key2]; }
295 | tileContainer.dataset.pending--;
296 | // console.log('Got ', key2, ' from _freshTiles');
297 | } else {
298 | this._tileCallbacks[key2] = this._tileCallbacks[key2] || [];
299 | this._tileCallbacks[key2].push( (function (c, k2) {
300 | return function(imgNode) {
301 | var parent = imgNode.parentNode;
302 | if (parent) {
303 | parent.removeChild(imgNode);
304 | parent.removeChild = L.Util.falseFn;
305 | // imgNode.parentNode.replaceChild(L.DomUtil.create('img'), imgNode);
306 | }
307 | c.appendChild(imgNode);
308 | c.dataset.pending--;
309 | if (!parseInt(c.dataset.pending)) { done(); }
310 | // console.log('Sent ', k2, ' to _tileCallbacks, still ', c.dataset.pending, ' images to go');
311 | }.bind(this)
312 | }.bind(this))(tileContainer, key2) );
313 | }
314 | }
315 |
316 | if (!parseInt(tileContainer.dataset.pending)) {
317 | L.Util.requestAnimFrame(done);
318 | }
319 | return tileContainer;
320 | },
321 |
322 | _checkZoomLevels: function () {
323 | //setting the zoom level on the Google map may result in a different zoom level than the one requested
324 | //(it won't go beyond the level for which they have data).
325 | // verify and make sure the zoom levels on both Leaflet and Google maps are consistent
326 | if ((this._map.getZoom() !== undefined) && (this._mutant.getZoom() !== this._map.getZoom())) {
327 | //zoom levels are out of sync. Set the leaflet zoom level to match the google one
328 | this._map.setZoom(this._mutant.getZoom());
329 | }
330 | },
331 |
332 | _reset: function () {
333 | this._initContainer();
334 | },
335 |
336 | _update: function () {
337 | L.GridLayer.prototype._update.call(this);
338 | if (!this._mutant) return;
339 |
340 | var center = this._map.getCenter();
341 | var _center = new google.maps.LatLng(center.lat, center.lng);
342 |
343 | this._mutant.setCenter(_center);
344 | var zoom = this._map.getZoom();
345 | if (zoom !== undefined) {
346 | this._mutant.setZoom(Math.round(this._map.getZoom()));
347 | }
348 | },
349 |
350 | _resize: function () {
351 | var size = this._map.getSize();
352 | if (this._mutantContainer.style.width === size.x &&
353 | this._mutantContainer.style.height === size.y)
354 | return;
355 | this.setElementSize(this._mutantContainer, size);
356 | if (!this._mutant) return;
357 | google.maps.event.trigger(this._mutant, 'resize');
358 | },
359 |
360 | _handleZoomAnim: function () {
361 | var center = this._map.getCenter();
362 | var _center = new google.maps.LatLng(center.lat, center.lng);
363 |
364 | this._mutant.setCenter(_center);
365 | this._mutant.setZoom(Math.round(this._map.getZoom()));
366 | }
367 | });
368 |
369 |
370 | // 🍂factory gridLayer.googleMutant(options)
371 | // Returns a new `GridLayer.GoogleMutant` given its options
372 | L.gridLayer.googleMutant = function(options) {
373 | return new L.GridLayer.GoogleMutant(options);
374 | };
375 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/leaflet.css:
--------------------------------------------------------------------------------
1 | /* required styles */
2 |
3 | .leaflet-pane,
4 | .leaflet-tile,
5 | .leaflet-marker-icon,
6 | .leaflet-marker-shadow,
7 | .leaflet-tile-container,
8 | .leaflet-map-pane svg,
9 | .leaflet-map-pane canvas,
10 | .leaflet-zoom-box,
11 | .leaflet-image-layer,
12 | .leaflet-layer {
13 | position: absolute;
14 | left: 0;
15 | top: 0;
16 | }
17 | .leaflet-container {
18 | overflow: hidden;
19 | }
20 | .leaflet-tile,
21 | .leaflet-marker-icon,
22 | .leaflet-marker-shadow {
23 | -webkit-user-select: none;
24 | -moz-user-select: none;
25 | user-select: none;
26 | -webkit-user-drag: none;
27 | }
28 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */
29 | .leaflet-safari .leaflet-tile {
30 | image-rendering: -webkit-optimize-contrast;
31 | }
32 | /* hack that prevents hw layers "stretching" when loading new tiles */
33 | .leaflet-safari .leaflet-tile-container {
34 | width: 1600px;
35 | height: 1600px;
36 | -webkit-transform-origin: 0 0;
37 | }
38 | .leaflet-marker-icon,
39 | .leaflet-marker-shadow {
40 | display: block;
41 | }
42 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
43 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
44 | .leaflet-container .leaflet-overlay-pane svg,
45 | .leaflet-container .leaflet-marker-pane img,
46 | .leaflet-container .leaflet-tile-pane img,
47 | .leaflet-container img.leaflet-image-layer {
48 | max-width: none !important;
49 | max-height: none !important;
50 | }
51 |
52 | .leaflet-container.leaflet-touch-zoom {
53 | -ms-touch-action: pan-x pan-y;
54 | touch-action: pan-x pan-y;
55 | }
56 | .leaflet-container.leaflet-touch-drag {
57 | -ms-touch-action: pinch-zoom;
58 | }
59 | .leaflet-container.leaflet-touch-drag.leaflet-touch-drag {
60 | -ms-touch-action: none;
61 | touch-action: none;
62 | }
63 | .leaflet-tile {
64 | filter: inherit;
65 | visibility: hidden;
66 | }
67 | .leaflet-tile-loaded {
68 | visibility: inherit;
69 | }
70 | .leaflet-zoom-box {
71 | width: 0;
72 | height: 0;
73 | -moz-box-sizing: border-box;
74 | box-sizing: border-box;
75 | z-index: 800;
76 | }
77 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
78 | .leaflet-overlay-pane svg {
79 | -moz-user-select: none;
80 | }
81 |
82 | .leaflet-pane { z-index: 400; }
83 |
84 | .leaflet-tile-pane { z-index: 200; }
85 | .leaflet-overlay-pane { z-index: 400; }
86 | .leaflet-shadow-pane { z-index: 500; }
87 | .leaflet-marker-pane { z-index: 600; }
88 | .leaflet-tooltip-pane { z-index: 650; }
89 | .leaflet-popup-pane { z-index: 700; }
90 |
91 | .leaflet-map-pane canvas { z-index: 100; }
92 | .leaflet-map-pane svg { z-index: 200; }
93 |
94 | .leaflet-vml-shape {
95 | width: 1px;
96 | height: 1px;
97 | }
98 | .lvml {
99 | behavior: url(#default#VML);
100 | display: inline-block;
101 | position: absolute;
102 | }
103 |
104 |
105 | /* control positioning */
106 |
107 | .leaflet-control {
108 | position: relative;
109 | z-index: 800;
110 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
111 | pointer-events: auto;
112 | }
113 | .leaflet-top,
114 | .leaflet-bottom {
115 | position: absolute;
116 | z-index: 1000;
117 | pointer-events: none;
118 | }
119 | .leaflet-top {
120 | top: 0;
121 | }
122 | .leaflet-right {
123 | right: 0;
124 | }
125 | .leaflet-bottom {
126 | bottom: 0;
127 | }
128 | .leaflet-left {
129 | left: 0;
130 | }
131 | .leaflet-control {
132 | float: left;
133 | clear: both;
134 | }
135 | .leaflet-right .leaflet-control {
136 | float: right;
137 | }
138 | .leaflet-top .leaflet-control {
139 | margin-top: 10px;
140 | }
141 | .leaflet-bottom .leaflet-control {
142 | margin-bottom: 10px;
143 | }
144 | .leaflet-left .leaflet-control {
145 | margin-left: 10px;
146 | }
147 | .leaflet-right .leaflet-control {
148 | margin-right: 10px;
149 | }
150 |
151 |
152 | /* zoom and fade animations */
153 |
154 | .leaflet-fade-anim .leaflet-tile {
155 | will-change: opacity;
156 | }
157 | .leaflet-fade-anim .leaflet-popup {
158 | opacity: 0;
159 | -webkit-transition: opacity 0.2s linear;
160 | -moz-transition: opacity 0.2s linear;
161 | -o-transition: opacity 0.2s linear;
162 | transition: opacity 0.2s linear;
163 | }
164 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
165 | opacity: 1;
166 | }
167 | .leaflet-zoom-animated {
168 | -webkit-transform-origin: 0 0;
169 | -ms-transform-origin: 0 0;
170 | transform-origin: 0 0;
171 | }
172 | .leaflet-zoom-anim .leaflet-zoom-animated {
173 | will-change: transform;
174 | }
175 | .leaflet-zoom-anim .leaflet-zoom-animated {
176 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
177 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
178 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
179 | transition: transform 0.25s cubic-bezier(0,0,0.25,1);
180 | }
181 | .leaflet-zoom-anim .leaflet-tile,
182 | .leaflet-pan-anim .leaflet-tile {
183 | -webkit-transition: none;
184 | -moz-transition: none;
185 | -o-transition: none;
186 | transition: none;
187 | }
188 |
189 | .leaflet-zoom-anim .leaflet-zoom-hide {
190 | visibility: hidden;
191 | }
192 |
193 |
194 | /* cursors */
195 |
196 | .leaflet-interactive {
197 | cursor: pointer;
198 | }
199 | .leaflet-grab {
200 | cursor: -webkit-grab;
201 | cursor: -moz-grab;
202 | }
203 | .leaflet-crosshair,
204 | .leaflet-crosshair .leaflet-interactive {
205 | cursor: crosshair;
206 | }
207 | .leaflet-popup-pane,
208 | .leaflet-control {
209 | cursor: auto;
210 | }
211 | .leaflet-dragging .leaflet-grab,
212 | .leaflet-dragging .leaflet-grab .leaflet-interactive,
213 | .leaflet-dragging .leaflet-marker-draggable {
214 | cursor: move;
215 | cursor: -webkit-grabbing;
216 | cursor: -moz-grabbing;
217 | }
218 |
219 | /* marker & overlays interactivity */
220 | .leaflet-marker-icon,
221 | .leaflet-marker-shadow,
222 | .leaflet-image-layer,
223 | .leaflet-pane > svg path,
224 | .leaflet-tile-container {
225 | pointer-events: none;
226 | }
227 |
228 | .leaflet-marker-icon.leaflet-interactive,
229 | .leaflet-image-layer.leaflet-interactive,
230 | .leaflet-pane > svg path.leaflet-interactive {
231 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
232 | pointer-events: auto;
233 | }
234 |
235 | /* visual tweaks */
236 |
237 | .leaflet-container {
238 | background: #ddd;
239 | outline: 0;
240 | }
241 | .leaflet-container a {
242 | color: #0078A8;
243 | }
244 | .leaflet-container a.leaflet-active {
245 | outline: 2px solid orange;
246 | }
247 | .leaflet-zoom-box {
248 | border: 2px dotted #38f;
249 | background: rgba(255,255,255,0.5);
250 | }
251 |
252 |
253 | /* general typography */
254 | .leaflet-container {
255 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
256 | }
257 |
258 |
259 | /* general toolbar styles */
260 |
261 | .leaflet-bar {
262 | box-shadow: 0 1px 5px rgba(0,0,0,0.65);
263 | border-radius: 4px;
264 | }
265 | .leaflet-bar a,
266 | .leaflet-bar a:hover {
267 | background-color: #fff;
268 | border-bottom: 1px solid #ccc;
269 | width: 26px;
270 | height: 26px;
271 | line-height: 26px;
272 | display: block;
273 | text-align: center;
274 | text-decoration: none;
275 | color: black;
276 | }
277 | .leaflet-bar a,
278 | .leaflet-control-layers-toggle {
279 | background-position: 50% 50%;
280 | background-repeat: no-repeat;
281 | display: block;
282 | }
283 | .leaflet-bar a:hover {
284 | background-color: #f4f4f4;
285 | }
286 | .leaflet-bar a:first-child {
287 | border-top-left-radius: 4px;
288 | border-top-right-radius: 4px;
289 | }
290 | .leaflet-bar a:last-child {
291 | border-bottom-left-radius: 4px;
292 | border-bottom-right-radius: 4px;
293 | border-bottom: none;
294 | }
295 | .leaflet-bar a.leaflet-disabled {
296 | cursor: default;
297 | background-color: #f4f4f4;
298 | color: #bbb;
299 | }
300 |
301 | .leaflet-touch .leaflet-bar a {
302 | width: 30px;
303 | height: 30px;
304 | line-height: 30px;
305 | }
306 |
307 |
308 | /* zoom control */
309 |
310 | .leaflet-control-zoom-in,
311 | .leaflet-control-zoom-out {
312 | font: bold 18px 'Lucida Console', Monaco, monospace;
313 | text-indent: 1px;
314 | }
315 | .leaflet-control-zoom-out {
316 | font-size: 20px;
317 | }
318 |
319 | .leaflet-touch .leaflet-control-zoom-in {
320 | font-size: 22px;
321 | }
322 | .leaflet-touch .leaflet-control-zoom-out {
323 | font-size: 24px;
324 | }
325 |
326 |
327 | /* layers control */
328 |
329 | .leaflet-control-layers {
330 | box-shadow: 0 1px 5px rgba(0,0,0,0.4);
331 | background: #fff;
332 | border-radius: 5px;
333 | }
334 | .leaflet-control-layers-toggle {
335 | background-image: url(images/layers.png);
336 | width: 36px;
337 | height: 36px;
338 | }
339 | .leaflet-retina .leaflet-control-layers-toggle {
340 | background-image: url(images/layers-2x.png);
341 | background-size: 26px 26px;
342 | }
343 | .leaflet-touch .leaflet-control-layers-toggle {
344 | width: 44px;
345 | height: 44px;
346 | }
347 | .leaflet-control-layers .leaflet-control-layers-list,
348 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle {
349 | display: none;
350 | }
351 | .leaflet-control-layers-expanded .leaflet-control-layers-list {
352 | display: block;
353 | position: relative;
354 | }
355 | .leaflet-control-layers-expanded {
356 | padding: 6px 10px 6px 6px;
357 | color: #333;
358 | background: #fff;
359 | }
360 | .leaflet-control-layers-scrollbar {
361 | overflow-y: scroll;
362 | padding-right: 5px;
363 | }
364 | .leaflet-control-layers-selector {
365 | margin-top: 2px;
366 | position: relative;
367 | top: 1px;
368 | }
369 | .leaflet-control-layers label {
370 | display: block;
371 | }
372 | .leaflet-control-layers-separator {
373 | height: 0;
374 | border-top: 1px solid #ddd;
375 | margin: 5px -10px 5px -6px;
376 | }
377 |
378 | /* Default icon URLs */
379 | .leaflet-default-icon-path {
380 | background-image: url(images/marker-icon.png);
381 | }
382 |
383 |
384 | /* attribution and scale controls */
385 |
386 | .leaflet-container .leaflet-control-attribution {
387 | background: #fff;
388 | background: rgba(255, 255, 255, 0.7);
389 | margin: 0;
390 | }
391 | .leaflet-control-attribution,
392 | .leaflet-control-scale-line {
393 | padding: 0 5px;
394 | color: #333;
395 | }
396 | .leaflet-control-attribution a {
397 | text-decoration: none;
398 | }
399 | .leaflet-control-attribution a:hover {
400 | text-decoration: underline;
401 | }
402 | .leaflet-container .leaflet-control-attribution,
403 | .leaflet-container .leaflet-control-scale {
404 | font-size: 11px;
405 | }
406 | .leaflet-left .leaflet-control-scale {
407 | margin-left: 5px;
408 | }
409 | .leaflet-bottom .leaflet-control-scale {
410 | margin-bottom: 5px;
411 | }
412 | .leaflet-control-scale-line {
413 | border: 2px solid #777;
414 | border-top: none;
415 | line-height: 1.1;
416 | padding: 2px 5px 1px;
417 | font-size: 11px;
418 | white-space: nowrap;
419 | overflow: hidden;
420 | -moz-box-sizing: border-box;
421 | box-sizing: border-box;
422 |
423 | background: #fff;
424 | background: rgba(255, 255, 255, 0.5);
425 | }
426 | .leaflet-control-scale-line:not(:first-child) {
427 | border-top: 2px solid #777;
428 | border-bottom: none;
429 | margin-top: -2px;
430 | }
431 | .leaflet-control-scale-line:not(:first-child):not(:last-child) {
432 | border-bottom: 2px solid #777;
433 | }
434 |
435 | .leaflet-touch .leaflet-control-attribution,
436 | .leaflet-touch .leaflet-control-layers,
437 | .leaflet-touch .leaflet-bar {
438 | box-shadow: none;
439 | }
440 | .leaflet-touch .leaflet-control-layers,
441 | .leaflet-touch .leaflet-bar {
442 | border: 2px solid rgba(0,0,0,0.2);
443 | background-clip: padding-box;
444 | }
445 |
446 |
447 | /* popup */
448 |
449 | .leaflet-popup {
450 | position: absolute;
451 | text-align: center;
452 | margin-bottom: 20px;
453 | }
454 | .leaflet-popup-content-wrapper {
455 | padding: 1px;
456 | text-align: left;
457 | border-radius: 12px;
458 | }
459 | .leaflet-popup-content {
460 | margin: 13px 19px;
461 | line-height: 1.4;
462 | }
463 | .leaflet-popup-content p {
464 | margin: 18px 0;
465 | }
466 | .leaflet-popup-tip-container {
467 | width: 40px;
468 | height: 20px;
469 | position: absolute;
470 | left: 50%;
471 | margin-left: -20px;
472 | overflow: hidden;
473 | pointer-events: none;
474 | }
475 | .leaflet-popup-tip {
476 | width: 17px;
477 | height: 17px;
478 | padding: 1px;
479 |
480 | margin: -10px auto 0;
481 |
482 | -webkit-transform: rotate(45deg);
483 | -moz-transform: rotate(45deg);
484 | -ms-transform: rotate(45deg);
485 | -o-transform: rotate(45deg);
486 | transform: rotate(45deg);
487 | }
488 | .leaflet-popup-content-wrapper,
489 | .leaflet-popup-tip {
490 | background: white;
491 | color: #333;
492 | box-shadow: 0 3px 14px rgba(0,0,0,0.4);
493 | }
494 | .leaflet-container a.leaflet-popup-close-button {
495 | position: absolute;
496 | top: 0;
497 | right: 0;
498 | padding: 4px 4px 0 0;
499 | border: none;
500 | text-align: center;
501 | width: 18px;
502 | height: 14px;
503 | font: 16px/14px Tahoma, Verdana, sans-serif;
504 | color: #c3c3c3;
505 | text-decoration: none;
506 | font-weight: bold;
507 | background: transparent;
508 | }
509 | .leaflet-container a.leaflet-popup-close-button:hover {
510 | color: #999;
511 | }
512 | .leaflet-popup-scrolled {
513 | overflow: auto;
514 | border-bottom: 1px solid #ddd;
515 | border-top: 1px solid #ddd;
516 | }
517 |
518 | .leaflet-oldie .leaflet-popup-content-wrapper {
519 | zoom: 1;
520 | }
521 | .leaflet-oldie .leaflet-popup-tip {
522 | width: 24px;
523 | margin: 0 auto;
524 |
525 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
526 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
527 | }
528 | .leaflet-oldie .leaflet-popup-tip-container {
529 | margin-top: -1px;
530 | }
531 |
532 | .leaflet-oldie .leaflet-control-zoom,
533 | .leaflet-oldie .leaflet-control-layers,
534 | .leaflet-oldie .leaflet-popup-content-wrapper,
535 | .leaflet-oldie .leaflet-popup-tip {
536 | border: 1px solid #999;
537 | }
538 |
539 |
540 | /* div icon */
541 |
542 | .leaflet-div-icon {
543 | background: #fff;
544 | border: 1px solid #666;
545 | }
546 |
547 |
548 | /* Tooltip */
549 | /* Base styles for the element that has a tooltip */
550 | .leaflet-tooltip {
551 | position: absolute;
552 | padding: 6px;
553 | background-color: #fff;
554 | border: 1px solid #fff;
555 | border-radius: 3px;
556 | color: #222;
557 | white-space: nowrap;
558 | -webkit-user-select: none;
559 | -moz-user-select: none;
560 | -ms-user-select: none;
561 | user-select: none;
562 | pointer-events: none;
563 | box-shadow: 0 1px 3px rgba(0,0,0,0.4);
564 | }
565 | .leaflet-tooltip.leaflet-clickable {
566 | cursor: pointer;
567 | pointer-events: auto;
568 | }
569 | .leaflet-tooltip-top:before,
570 | .leaflet-tooltip-bottom:before,
571 | .leaflet-tooltip-left:before,
572 | .leaflet-tooltip-right:before {
573 | position: absolute;
574 | pointer-events: none;
575 | border: 6px solid transparent;
576 | background: transparent;
577 | content: "";
578 | }
579 |
580 | /* Directions */
581 |
582 | .leaflet-tooltip-bottom {
583 | margin-top: 6px;
584 | }
585 | .leaflet-tooltip-top {
586 | margin-top: -6px;
587 | }
588 | .leaflet-tooltip-bottom:before,
589 | .leaflet-tooltip-top:before {
590 | left: 50%;
591 | margin-left: -6px;
592 | }
593 | .leaflet-tooltip-top:before {
594 | bottom: 0;
595 | margin-bottom: -12px;
596 | border-top-color: #fff;
597 | }
598 | .leaflet-tooltip-bottom:before {
599 | top: 0;
600 | margin-top: -12px;
601 | margin-left: -6px;
602 | border-bottom-color: #fff;
603 | }
604 | .leaflet-tooltip-left {
605 | margin-left: -6px;
606 | }
607 | .leaflet-tooltip-right {
608 | margin-left: 6px;
609 | }
610 | .leaflet-tooltip-left:before,
611 | .leaflet-tooltip-right:before {
612 | top: 50%;
613 | margin-top: -6px;
614 | }
615 | .leaflet-tooltip-left:before {
616 | right: 0;
617 | margin-right: -12px;
618 | border-left-color: #fff;
619 | }
620 | .leaflet-tooltip-right:before {
621 | left: 0;
622 | margin-left: -12px;
623 | border-right-color: #fff;
624 | }
625 |
--------------------------------------------------------------------------------
/view/frontend/web/leaflet/plugins/markercluster/leaflet.markercluster.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e.Leaflet=e.Leaflet||{},e.Leaflet.markercluster=e.Leaflet.markercluster||{}))}(this,function(e){"use strict";var t=L.MarkerClusterGroup=L.FeatureGroup.extend({options:{maxClusterRadius:80,iconCreateFunction:null,clusterPane:L.Marker.prototype.options.pane,spiderfyOnMaxZoom:!0,showCoverageOnHover:!0,zoomToBoundsOnClick:!0,singleMarkerMode:!1,disableClusteringAtZoom:null,removeOutsideVisibleBounds:!0,animate:!0,animateAddingMarkers:!1,spiderfyDistanceMultiplier:1,spiderLegPolylineOptions:{weight:1.5,color:"#222",opacity:.5},chunkedLoading:!1,chunkInterval:200,chunkDelay:50,chunkProgress:null,polygonOptions:{}},initialize:function(e){L.Util.setOptions(this,e),this.options.iconCreateFunction||(this.options.iconCreateFunction=this._defaultIconCreateFunction),this._featureGroup=L.featureGroup(),this._featureGroup.addEventParent(this),this._nonPointGroup=L.featureGroup(),this._nonPointGroup.addEventParent(this),this._inZoomAnimation=0,this._needsClustering=[],this._needsRemoving=[],this._currentShownBounds=null,this._queue=[],this._childMarkerEventHandlers={dragstart:this._childMarkerDragStart,move:this._childMarkerMoved,dragend:this._childMarkerDragEnd};var t=L.DomUtil.TRANSITION&&this.options.animate;L.extend(this,t?this._withAnimation:this._noAnimation),this._markerCluster=t?L.MarkerCluster:L.MarkerClusterNonAnimated},addLayer:function(e){if(e instanceof L.LayerGroup)return this.addLayers([e]);if(!e.getLatLng)return this._nonPointGroup.addLayer(e),this.fire("layeradd",{layer:e}),this;if(!this._map)return this._needsClustering.push(e),this.fire("layeradd",{layer:e}),this;if(this.hasLayer(e))return this;this._unspiderfy&&this._unspiderfy(),this._addLayer(e,this._maxZoom),this.fire("layeradd",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons();var t=e,i=this._zoom;if(e.__parent)for(;t.__parent._zoom>=i;)t=t.__parent;return this._currentShownBounds.contains(t.getLatLng())&&(this.options.animateAddingMarkers?this._animationAddLayer(e,t):this._animationAddLayerNonAnimated(e,t)),this},removeLayer:function(e){return e instanceof L.LayerGroup?this.removeLayers([e]):e.getLatLng?this._map?e.__parent?(this._unspiderfy&&(this._unspiderfy(),this._unspiderfyLayer(e)),this._removeLayer(e,!0),this.fire("layerremove",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),e.off(this._childMarkerEventHandlers,this),this._featureGroup.hasLayer(e)&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow()),this):this:(!this._arraySplice(this._needsClustering,e)&&this.hasLayer(e)&&this._needsRemoving.push({layer:e,latlng:e._latlng}),this.fire("layerremove",{layer:e}),this):(this._nonPointGroup.removeLayer(e),this.fire("layerremove",{layer:e}),this)},addLayers:function(e,t){if(!L.Util.isArray(e))return this.addLayer(e);var i,n=this._featureGroup,r=this._nonPointGroup,s=this.options.chunkedLoading,o=this.options.chunkInterval,a=this.options.chunkProgress,h=e.length,l=0,u=!0;if(this._map){var _=(new Date).getTime(),d=L.bind(function(){for(var c=(new Date).getTime();h>l;l++){if(s&&0===l%200){var p=(new Date).getTime()-c;if(p>o)break}if(i=e[l],i instanceof L.LayerGroup)u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length;else if(i.getLatLng){if(!this.hasLayer(i)&&(this._addLayer(i,this._maxZoom),t||this.fire("layeradd",{layer:i}),i.__parent&&2===i.__parent.getChildCount())){var f=i.__parent.getAllChildMarkers(),m=f[0]===i?f[1]:f[0];n.removeLayer(m)}}else r.addLayer(i),t||this.fire("layeradd",{layer:i})}a&&a(l,h,(new Date).getTime()-_),l===h?(this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds)):setTimeout(d,this.options.chunkDelay)},this);d()}else for(var c=this._needsClustering;h>l;l++)i=e[l],i instanceof L.LayerGroup?(u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length):i.getLatLng?this.hasLayer(i)||c.push(i):r.addLayer(i);return this},removeLayers:function(e){var t,i,n=e.length,r=this._featureGroup,s=this._nonPointGroup,o=!0;if(!this._map){for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):(this._arraySplice(this._needsClustering,i),s.removeLayer(i),this.hasLayer(i)&&this._needsRemoving.push({layer:i,latlng:i._latlng}),this.fire("layerremove",{layer:i}));return this}if(this._unspiderfy){this._unspiderfy();var a=e.slice(),h=n;for(t=0;h>t;t++)i=a[t],i instanceof L.LayerGroup?(this._extractNonGroupLayers(i,a),h=a.length):this._unspiderfyLayer(i)}for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):i.__parent?(this._removeLayer(i,!0,!0),this.fire("layerremove",{layer:i}),r.hasLayer(i)&&(r.removeLayer(i),i.clusterShow&&i.clusterShow())):(s.removeLayer(i),this.fire("layerremove",{layer:i}));return this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds),this},clearLayers:function(){return this._map||(this._needsClustering=[],this._needsRemoving=[],delete this._gridClusters,delete this._gridUnclustered),this._noanimationUnspiderfy&&this._noanimationUnspiderfy(),this._featureGroup.clearLayers(),this._nonPointGroup.clearLayers(),this.eachLayer(function(e){e.off(this._childMarkerEventHandlers,this),delete e.__parent},this),this._map&&this._generateInitialClusters(),this},getBounds:function(){var e=new L.LatLngBounds;this._topClusterLevel&&e.extend(this._topClusterLevel._bounds);for(var t=this._needsClustering.length-1;t>=0;t--)e.extend(this._needsClustering[t].getLatLng());return e.extend(this._nonPointGroup.getBounds()),e},eachLayer:function(e,t){var i,n,r,s=this._needsClustering.slice(),o=this._needsRemoving;for(this._topClusterLevel&&this._topClusterLevel.getAllChildMarkers(s),n=s.length-1;n>=0;n--){for(i=!0,r=o.length-1;r>=0;r--)if(o[r].layer===s[n]){i=!1;break}i&&e.call(t,s[n])}this._nonPointGroup.eachLayer(e,t)},getLayers:function(){var e=[];return this.eachLayer(function(t){e.push(t)}),e},getLayer:function(e){var t=null;return e=parseInt(e,10),this.eachLayer(function(i){L.stamp(i)===e&&(t=i)}),t},hasLayer:function(e){if(!e)return!1;var t,i=this._needsClustering;for(t=i.length-1;t>=0;t--)if(i[t]===e)return!0;for(i=this._needsRemoving,t=i.length-1;t>=0;t--)if(i[t].layer===e)return!1;return!(!e.__parent||e.__parent._group!==this)||this._nonPointGroup.hasLayer(e)},zoomToShowLayer:function(e,t){"function"!=typeof t&&(t=function(){});var i=function(){!e._icon&&!e.__parent._icon||this._inZoomAnimation||(this._map.off("moveend",i,this),this.off("animationend",i,this),e._icon?t():e.__parent._icon&&(this.once("spiderfied",t,this),e.__parent.spiderfy()))};e._icon&&this._map.getBounds().contains(e.getLatLng())?t():e.__parent._zoomt;t++)n=this._needsRemoving[t],n.newlatlng=n.layer._latlng,n.layer._latlng=n.latlng;for(t=0,i=this._needsRemoving.length;i>t;t++)n=this._needsRemoving[t],this._removeLayer(n.layer,!0),n.layer._latlng=n.newlatlng;this._needsRemoving=[],this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds(),this._map.on("zoomend",this._zoomEnd,this),this._map.on("moveend",this._moveEnd,this),this._spiderfierOnAdd&&this._spiderfierOnAdd(),this._bindEvents(),i=this._needsClustering,this._needsClustering=[],this.addLayers(i,!0)},onRemove:function(e){e.off("zoomend",this._zoomEnd,this),e.off("moveend",this._moveEnd,this),this._unbindEvents(),this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim",""),this._spiderfierOnRemove&&this._spiderfierOnRemove(),delete this._maxLat,this._hideCoverage(),this._featureGroup.remove(),this._nonPointGroup.remove(),this._featureGroup.clearLayers(),this._map=null},getVisibleParent:function(e){for(var t=e;t&&!t._icon;)t=t.__parent;return t||null},_arraySplice:function(e,t){for(var i=e.length-1;i>=0;i--)if(e[i]===t)return e.splice(i,1),!0},_removeFromGridUnclustered:function(e,t){for(var i=this._map,n=this._gridUnclustered,r=Math.floor(this._map.getMinZoom());t>=r&&n[t].removeObject(e,i.project(e.getLatLng(),t));t--);},_childMarkerDragStart:function(e){e.target.__dragStart=e.target._latlng},_childMarkerMoved:function(e){if(!this._ignoreMove&&!e.target.__dragStart){var t=e.target._popup&&e.target._popup.isOpen();this._moveChild(e.target,e.oldLatLng,e.latlng),t&&e.target.openPopup()}},_moveChild:function(e,t,i){e._latlng=t,this.removeLayer(e),e._latlng=i,this.addLayer(e)},_childMarkerDragEnd:function(e){e.target.__dragStart&&this._moveChild(e.target,e.target.__dragStart,e.target._latlng),delete e.target.__dragStart},_removeLayer:function(e,t,i){var n=this._gridClusters,r=this._gridUnclustered,s=this._featureGroup,o=this._map,a=Math.floor(this._map.getMinZoom());t&&this._removeFromGridUnclustered(e,this._maxZoom);var h,l=e.__parent,u=l._markers;for(this._arraySplice(u,e);l&&(l._childCount--,l._boundsNeedUpdate=!0,!(l._zoomt?"small":100>t?"medium":"large",new L.DivIcon({html:""+t+"
",className:"marker-cluster"+i,iconSize:new L.Point(40,40)})},_bindEvents:function(){var e=this._map,t=this.options.spiderfyOnMaxZoom,i=this.options.showCoverageOnHover,n=this.options.zoomToBoundsOnClick;(t||n)&&this.on("clusterclick",this._zoomOrSpiderfy,this),i&&(this.on("clustermouseover",this._showCoverage,this),this.on("clustermouseout",this._hideCoverage,this),e.on("zoomend",this._hideCoverage,this))},_zoomOrSpiderfy:function(e){for(var t=e.layer,i=t;1===i._childClusters.length;)i=i._childClusters[0];i._zoom===this._maxZoom&&i._childCount===t._childCount&&this.options.spiderfyOnMaxZoom?t.spiderfy():this.options.zoomToBoundsOnClick&&t.zoomToBounds(),e.originalEvent&&13===e.originalEvent.keyCode&&this._map._container.focus()},_showCoverage:function(e){var t=this._map;this._inZoomAnimation||(this._shownPolygon&&t.removeLayer(this._shownPolygon),e.layer.getChildCount()>2&&e.layer!==this._spiderfied&&(this._shownPolygon=new L.Polygon(e.layer.getConvexHull(),this.options.polygonOptions),t.addLayer(this._shownPolygon)))},_hideCoverage:function(){this._shownPolygon&&(this._map.removeLayer(this._shownPolygon),this._shownPolygon=null)},_unbindEvents:function(){var e=this.options.spiderfyOnMaxZoom,t=this.options.showCoverageOnHover,i=this.options.zoomToBoundsOnClick,n=this._map;(e||i)&&this.off("clusterclick",this._zoomOrSpiderfy,this),t&&(this.off("clustermouseover",this._showCoverage,this),this.off("clustermouseout",this._hideCoverage,this),n.off("zoomend",this._hideCoverage,this))},_zoomEnd:function(){this._map&&(this._mergeSplitClusters(),this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds())},_moveEnd:function(){if(!this._inZoomAnimation){var e=this._getExpandedVisibleBounds();this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),this._zoom,e),this._topClusterLevel._recursivelyAddChildrenToMap(null,Math.round(this._map._zoom),e),this._currentShownBounds=e}},_generateInitialClusters:function(){var e=Math.ceil(this._map.getMaxZoom()),t=Math.floor(this._map.getMinZoom()),i=this.options.maxClusterRadius,n=i;"function"!=typeof i&&(n=function(){return i}),null!==this.options.disableClusteringAtZoom&&(e=this.options.disableClusteringAtZoom-1),this._maxZoom=e,this._gridClusters={},this._gridUnclustered={};for(var r=e;r>=t;r--)this._gridClusters[r]=new L.DistanceGrid(n(r)),this._gridUnclustered[r]=new L.DistanceGrid(n(r));this._topClusterLevel=new this._markerCluster(this,t-1)},_addLayer:function(e,t){var i,n,r=this._gridClusters,s=this._gridUnclustered,o=Math.floor(this._map.getMinZoom());for(this.options.singleMarkerMode&&this._overrideMarkerIcon(e),e.on(this._childMarkerEventHandlers,this);t>=o;t--){i=this._map.project(e.getLatLng(),t);var a=r[t].getNearObject(i);if(a)return a._addChild(e),e.__parent=a,void 0;if(a=s[t].getNearObject(i)){var h=a.__parent;h&&this._removeLayer(a,!1);var l=new this._markerCluster(this,t,a,e);r[t].addObject(l,this._map.project(l._cLatLng,t)),a.__parent=l,e.__parent=l;var u=l;for(n=t-1;n>h._zoom;n--)u=new this._markerCluster(this,n,u),r[n].addObject(u,this._map.project(a.getLatLng(),n));return h._addChild(u),this._removeFromGridUnclustered(a,t),void 0}s[t].addObject(e,i)}this._topClusterLevel._addChild(e),e.__parent=this._topClusterLevel},_refreshClustersIcons:function(){this._featureGroup.eachLayer(function(e){e instanceof L.MarkerCluster&&e._iconNeedsUpdate&&e._updateIcon()})},_enqueue:function(e){this._queue.push(e),this._queueTimeout||(this._queueTimeout=setTimeout(L.bind(this._processQueue,this),300))},_processQueue:function(){for(var e=0;ee?(this._animationStart(),this._animationZoomOut(this._zoom,e)):this._moveEnd()},_getExpandedVisibleBounds:function(){return this.options.removeOutsideVisibleBounds?L.Browser.mobile?this._checkBoundsMaxLat(this._map.getBounds()):this._checkBoundsMaxLat(this._map.getBounds().pad(1)):this._mapBoundsInfinite},_checkBoundsMaxLat:function(e){var t=this._maxLat;return void 0!==t&&(e.getNorth()>=t&&(e._northEast.lat=1/0),e.getSouth()<=-t&&(e._southWest.lat=-1/0)),e},_animationAddLayerNonAnimated:function(e,t){if(t===e)this._featureGroup.addLayer(e);else if(2===t._childCount){t._addToMap();var i=t.getAllChildMarkers();this._featureGroup.removeLayer(i[0]),this._featureGroup.removeLayer(i[1])}else t._updateIcon()},_extractNonGroupLayers:function(e,t){var i,n=e.getLayers(),r=0;for(t=t||[];r=0;i--)o=h[i],n.contains(o._latlng)||r.removeLayer(o)}),this._forceLayout(),this._topClusterLevel._recursivelyBecomeVisible(n,t),r.eachLayer(function(e){e instanceof L.MarkerCluster||!e._icon||e.clusterShow()}),this._topClusterLevel._recursively(n,e,t,function(e){e._recursivelyRestoreChildPositions(t)}),this._ignoreMove=!1,this._enqueue(function(){this._topClusterLevel._recursively(n,e,s,function(e){r.removeLayer(e),e.clusterShow()}),this._animationEnd()})},_animationZoomOut:function(e,t){this._animationZoomOutSingle(this._topClusterLevel,e-1,t),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),e,this._getExpandedVisibleBounds())},_animationAddLayer:function(e,t){var i=this,n=this._featureGroup;n.addLayer(e),t!==e&&(t._childCount>2?(t._updateIcon(),this._forceLayout(),this._animationStart(),e._setPos(this._map.latLngToLayerPoint(t.getLatLng())),e.clusterHide(),this._enqueue(function(){n.removeLayer(e),e.clusterShow(),i._animationEnd()})):(this._forceLayout(),i._animationStart(),i._animationZoomOutSingle(t,this._map.getMaxZoom(),this._zoom)))}},_animationZoomOutSingle:function(e,t,i){var n=this._getExpandedVisibleBounds(),r=Math.floor(this._map.getMinZoom());e._recursivelyAnimateChildrenInAndAddSelfToMap(n,r,t+1,i);var s=this;this._forceLayout(),e._recursivelyBecomeVisible(n,i),this._enqueue(function(){if(1===e._childCount){var o=e._markers[0];this._ignoreMove=!0,o.setLatLng(o.getLatLng()),this._ignoreMove=!1,o.clusterShow&&o.clusterShow()}else e._recursively(n,i,r,function(e){e._recursivelyRemoveChildrenFromMap(n,r,t+1)});s._animationEnd()})},_animationEnd:function(){this._map&&(this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim","")),this._inZoomAnimation--,this.fire("animationend")},_forceLayout:function(){L.Util.falseFn(document.body.offsetWidth)}}),L.markerClusterGroup=function(e){return new L.MarkerClusterGroup(e)};var i=L.MarkerCluster=L.Marker.extend({options:L.Icon.prototype.options,initialize:function(e,t,i,n){L.Marker.prototype.initialize.call(this,i?i._cLatLng||i.getLatLng():new L.LatLng(0,0),{icon:this,pane:e.options.clusterPane}),this._group=e,this._zoom=t,this._markers=[],this._childClusters=[],this._childCount=0,this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._bounds=new L.LatLngBounds,i&&this._addChild(i),n&&this._addChild(n)},getAllChildMarkers:function(e){e=e||[];for(var t=this._childClusters.length-1;t>=0;t--)this._childClusters[t].getAllChildMarkers(e);for(var i=this._markers.length-1;i>=0;i--)e.push(this._markers[i]);return e},getChildCount:function(){return this._childCount},zoomToBounds:function(e){for(var t,i=this._childClusters.slice(),n=this._group._map,r=n.getBoundsZoom(this._bounds),s=this._zoom+1,o=n.getZoom();i.length>0&&r>s;){s++;var a=[];for(t=0;ts?this._group._map.setView(this._latlng,s):o>=r?this._group._map.setView(this._latlng,o+1):this._group._map.fitBounds(this._bounds,e)},getBounds:function(){var e=new L.LatLngBounds;return e.extend(this._bounds),e},_updateIcon:function(){this._iconNeedsUpdate=!0,this._icon&&this.setIcon(this)},createIcon:function(){return this._iconNeedsUpdate&&(this._iconObj=this._group.options.iconCreateFunction(this),this._iconNeedsUpdate=!1),this._iconObj.createIcon()},createShadow:function(){return this._iconObj.createShadow()},_addChild:function(e,t){this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._setClusterCenter(e),e instanceof L.MarkerCluster?(t||(this._childClusters.push(e),e.__parent=this),this._childCount+=e._childCount):(t||this._markers.push(e),this._childCount++),this.__parent&&this.__parent._addChild(e,!0)},_setClusterCenter:function(e){this._cLatLng||(this._cLatLng=e._cLatLng||e._latlng)},_resetBounds:function(){var e=this._bounds;e._southWest&&(e._southWest.lat=1/0,e._southWest.lng=1/0),e._northEast&&(e._northEast.lat=-1/0,e._northEast.lng=-1/0)},_recalculateBounds:function(){var e,t,i,n,r=this._markers,s=this._childClusters,o=0,a=0,h=this._childCount;if(0!==h){for(this._resetBounds(),e=0;e=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())},function(e){var i,n,r=e._childClusters;for(i=r.length-1;i>=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())})},_recursivelyAnimateChildrenInAndAddSelfToMap:function(e,t,i,n){this._recursively(e,n,t,function(r){r._recursivelyAnimateChildrenIn(e,r._group._map.latLngToLayerPoint(r.getLatLng()).round(),i),r._isSingleParent()&&i-1===n?(r.clusterShow(),r._recursivelyRemoveChildrenFromMap(e,t,i)):r.clusterHide(),r._addToMap()})},_recursivelyBecomeVisible:function(e,t){this._recursively(e,this._group._map.getMinZoom(),t,null,function(e){e.clusterShow()})},_recursivelyAddChildrenToMap:function(e,t,i){this._recursively(i,this._group._map.getMinZoom()-1,t,function(n){if(t!==n._zoom)for(var r=n._markers.length-1;r>=0;r--){var s=n._markers[r];i.contains(s._latlng)&&(e&&(s._backupLatlng=s.getLatLng(),s.setLatLng(e),s.clusterHide&&s.clusterHide()),n._group._featureGroup.addLayer(s))}},function(t){t._addToMap(e)})},_recursivelyRestoreChildPositions:function(e){for(var t=this._markers.length-1;t>=0;t--){var i=this._markers[t];i._backupLatlng&&(i.setLatLng(i._backupLatlng),delete i._backupLatlng)}if(e-1===this._zoom)for(var n=this._childClusters.length-1;n>=0;n--)this._childClusters[n]._restorePosition();else for(var r=this._childClusters.length-1;r>=0;r--)this._childClusters[r]._recursivelyRestoreChildPositions(e)},_restorePosition:function(){this._backupLatlng&&(this.setLatLng(this._backupLatlng),delete this._backupLatlng)},_recursivelyRemoveChildrenFromMap:function(e,t,i,n){var r,s;this._recursively(e,t-1,i-1,function(e){for(s=e._markers.length-1;s>=0;s--)r=e._markers[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())},function(e){for(s=e._childClusters.length-1;s>=0;s--)r=e._childClusters[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())})},_recursively:function(e,t,i,n,r){var s,o,a=this._childClusters,h=this._zoom;if(h>=t&&(n&&n(this),r&&h===i&&r(this)),t>h||i>h)for(s=a.length-1;s>=0;s--)o=a[s],o._boundsNeedUpdate&&o._recalculateBounds(),e.intersects(o._bounds)&&o._recursively(e,t,i,n,r)},_isSingleParent:function(){return this._childClusters.length>0&&this._childClusters[0]._childCount===this._childCount}});L.Marker.include({clusterHide:function(){var e=this.options.opacity;return this.setOpacity(0),this.options.opacity=e,this},clusterShow:function(){return this.setOpacity(this.options.opacity)}}),L.DistanceGrid=function(e){this._cellSize=e,this._sqCellSize=e*e,this._grid={},this._objectPoint={}},L.DistanceGrid.prototype={addObject:function(e,t){var i=this._getCoord(t.x),n=this._getCoord(t.y),r=this._grid,s=r[n]=r[n]||{},o=s[i]=s[i]||[],a=L.Util.stamp(e);this._objectPoint[a]=t,o.push(e)},updateObject:function(e,t){this.removeObject(e),this.addObject(e,t)},removeObject:function(e,t){var i,n,r=this._getCoord(t.x),s=this._getCoord(t.y),o=this._grid,a=o[s]=o[s]||{},h=a[r]=a[r]||[];for(delete this._objectPoint[L.Util.stamp(e)],i=0,n=h.length;n>i;i++)if(h[i]===e)return h.splice(i,1),1===n&&delete a[r],!0},eachObject:function(e,t){var i,n,r,s,o,a,h,l=this._grid;for(i in l){o=l[i];for(n in o)for(a=o[n],r=0,s=a.length;s>r;r++)h=e.call(t,a[r]),h&&(r--,s--)}},getNearObject:function(e){var t,i,n,r,s,o,a,h,l=this._getCoord(e.x),u=this._getCoord(e.y),_=this._objectPoint,d=this._sqCellSize,c=null;for(t=u-1;u+1>=t;t++)if(r=this._grid[t])for(i=l-1;l+1>=i;i++)if(s=r[i])for(n=0,o=s.length;o>n;n++)a=s[n],h=this._sqDist(_[L.Util.stamp(a)],e),(d>h||d>=h&&null===c)&&(d=h,c=a);return c},_getCoord:function(e){var t=Math.floor(e/this._cellSize);return isFinite(t)?t:e},_sqDist:function(e,t){var i=t.x-e.x,n=t.y-e.y;return i*i+n*n}},function(){L.QuickHull={getDistant:function(e,t){var i=t[1].lat-t[0].lat,n=t[0].lng-t[1].lng;return n*(e.lat-t[0].lat)+i*(e.lng-t[0].lng)},findMostDistantPointFromBaseLine:function(e,t){var i,n,r,s=0,o=null,a=[];for(i=t.length-1;i>=0;i--)n=t[i],r=this.getDistant(n,e),r>0&&(a.push(n),r>s&&(s=r,o=n));return{maxPoint:o,newPoints:a}},buildConvexHull:function(e,t){var i=[],n=this.findMostDistantPointFromBaseLine(e,t);return n.maxPoint?(i=i.concat(this.buildConvexHull([e[0],n.maxPoint],n.newPoints)),i=i.concat(this.buildConvexHull([n.maxPoint,e[1]],n.newPoints))):[e[0]]},getConvexHull:function(e){var t,i=!1,n=!1,r=!1,s=!1,o=null,a=null,h=null,l=null,u=null,_=null;for(t=e.length-1;t>=0;t--){var d=e[t];(i===!1||d.lat>i)&&(o=d,i=d.lat),(n===!1||d.latr)&&(h=d,r=d.lng),(s===!1||d.lng=0;t--)e=i[t].getLatLng(),n.push(e);return L.QuickHull.getConvexHull(n)}}),L.MarkerCluster.include({_2PI:2*Math.PI,_circleFootSeparation:25,_circleStartAngle:0,_spiralFootSeparation:28,_spiralLengthStart:11,_spiralLengthFactor:5,_circleSpiralSwitchover:9,spiderfy:function(){if(this._group._spiderfied!==this&&!this._group._inZoomAnimation){var e,t=this.getAllChildMarkers(),i=this._group,n=i._map,r=n.latLngToLayerPoint(this._latlng);this._group._unspiderfy(),this._group._spiderfied=this,t.length>=this._circleSpiralSwitchover?e=this._generatePointsSpiral(t.length,r):(r.y+=10,e=this._generatePointsCircle(t.length,r)),this._animationSpiderfy(t,e)}},unspiderfy:function(e){this._group._inZoomAnimation||(this._animationUnspiderfy(e),this._group._spiderfied=null)},_generatePointsCircle:function(e,t){var i,n,r=this._group.options.spiderfyDistanceMultiplier*this._circleFootSeparation*(2+e),s=r/this._2PI,o=this._2PI/e,a=[];for(s=Math.max(s,35),a.length=e,i=0;e>i;i++)n=this._circleStartAngle+i*o,a[i]=new L.Point(t.x+s*Math.cos(n),t.y+s*Math.sin(n))._round();return a},_generatePointsSpiral:function(e,t){var i,n=this._group.options.spiderfyDistanceMultiplier,r=n*this._spiralLengthStart,s=n*this._spiralFootSeparation,o=n*this._spiralLengthFactor*this._2PI,a=0,h=[];for(h.length=e,i=e;i>=0;i--)e>i&&(h[i]=new L.Point(t.x+r*Math.cos(a),t.y+r*Math.sin(a))._round()),a+=s/r+5e-4*i,r+=o/a;return h},_noanimationUnspiderfy:function(){var e,t,i=this._group,n=i._map,r=i._featureGroup,s=this.getAllChildMarkers();for(i._ignoreMove=!0,this.setOpacity(1),t=s.length-1;t>=0;t--)e=s[t],r.removeLayer(e),e._preSpiderfyLatlng&&(e.setLatLng(e._preSpiderfyLatlng),delete e._preSpiderfyLatlng),e.setZIndexOffset&&e.setZIndexOffset(0),e._spiderLeg&&(n.removeLayer(e._spiderLeg),delete e._spiderLeg);i.fire("unspiderfied",{cluster:this,markers:s}),i._ignoreMove=!1,i._spiderfied=null}}),L.MarkerClusterNonAnimated=L.MarkerCluster.extend({_animationSpiderfy:function(e,t){var i,n,r,s,o=this._group,a=o._map,h=o._featureGroup,l=this._group.options.spiderLegPolylineOptions;for(o._ignoreMove=!0,i=0;i=0;i--)a=u.layerPointToLatLng(t[i]),n=e[i],n._preSpiderfyLatlng=n._latlng,n.setLatLng(a),n.clusterShow&&n.clusterShow(),p&&(r=n._spiderLeg,s=r._path,s.style.strokeDashoffset=0,r.setStyle({opacity:m}));this.setOpacity(.3),l._ignoreMove=!1,setTimeout(function(){l._animationEnd(),l.fire("spiderfied",{cluster:h,markers:e})},200)},_animationUnspiderfy:function(e){var t,i,n,r,s,o,a=this,h=this._group,l=h._map,u=h._featureGroup,_=e?l._latLngToNewLayerPoint(this._latlng,e.zoom,e.center):l.latLngToLayerPoint(this._latlng),d=this.getAllChildMarkers(),c=L.Path.SVG;for(h._ignoreMove=!0,h._animationStart(),this.setOpacity(1),i=d.length-1;i>=0;i--)t=d[i],t._preSpiderfyLatlng&&(t.closePopup(),t.setLatLng(t._preSpiderfyLatlng),delete t._preSpiderfyLatlng,o=!0,t._setPos&&(t._setPos(_),o=!1),t.clusterHide&&(t.clusterHide(),o=!1),o&&u.removeLayer(t),c&&(n=t._spiderLeg,r=n._path,s=r.getTotalLength()+.1,r.style.strokeDashoffset=s,n.setStyle({opacity:0})));h._ignoreMove=!1,setTimeout(function(){var e=0;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&e++;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&(t.clusterShow&&t.clusterShow(),t.setZIndexOffset&&t.setZIndexOffset(0),e>1&&u.removeLayer(t),l.removeLayer(t._spiderLeg),delete t._spiderLeg);h._animationEnd(),h.fire("unspiderfied",{cluster:a,markers:d})},200)}}),L.MarkerClusterGroup.include({_spiderfied:null,unspiderfy:function(){this._unspiderfy.apply(this,arguments)},_spiderfierOnAdd:function(){this._map.on("click",this._unspiderfyWrapper,this),this._map.options.zoomAnimation&&this._map.on("zoomstart",this._unspiderfyZoomStart,this),this._map.on("zoomend",this._noanimationUnspiderfy,this),L.Browser.touch||this._map.getRenderer(this)},_spiderfierOnRemove:function(){this._map.off("click",this._unspiderfyWrapper,this),this._map.off("zoomstart",this._unspiderfyZoomStart,this),this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._map.off("zoomend",this._noanimationUnspiderfy,this),this._noanimationUnspiderfy()},_unspiderfyZoomStart:function(){this._map&&this._map.on("zoomanim",this._unspiderfyZoomAnim,this)
2 | },_unspiderfyZoomAnim:function(e){L.DomUtil.hasClass(this._map._mapPane,"leaflet-touching")||(this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._unspiderfy(e))},_unspiderfyWrapper:function(){this._unspiderfy()},_unspiderfy:function(e){this._spiderfied&&this._spiderfied.unspiderfy(e)},_noanimationUnspiderfy:function(){this._spiderfied&&this._spiderfied._noanimationUnspiderfy()},_unspiderfyLayer:function(e){e._spiderLeg&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow(),e.setZIndexOffset&&e.setZIndexOffset(0),this._map.removeLayer(e._spiderLeg),delete e._spiderLeg)}}),L.MarkerClusterGroup.include({refreshClusters:function(e){return e?e instanceof L.MarkerClusterGroup?e=e._topClusterLevel.getAllChildMarkers():e instanceof L.LayerGroup?e=e._layers:e instanceof L.MarkerCluster?e=e.getAllChildMarkers():e instanceof L.Marker&&(e=[e]):e=this._topClusterLevel.getAllChildMarkers(),this._flagParentsIconsNeedUpdate(e),this._refreshClustersIcons(),this.options.singleMarkerMode&&this._refreshSingleMarkerModeMarkers(e),this},_flagParentsIconsNeedUpdate:function(e){var t,i;for(t in e)for(i=e[t].__parent;i;)i._iconNeedsUpdate=!0,i=i.__parent},_refreshSingleMarkerModeMarkers:function(e){var t,i;for(t in e)i=e[t],this.hasLayer(i)&&i.setIcon(this._overrideMarkerIcon(i))}}),L.Marker.include({refreshIconOptions:function(e,t){var i=this.options.icon;return L.setOptions(i,e),this.setIcon(i),t&&this.__parent&&this.__parent._group.refreshClusters(this),this}}),e.MarkerClusterGroup=t,e.MarkerCluster=i});
3 | //# sourceMappingURL=leaflet.markercluster.js.map
--------------------------------------------------------------------------------