Current Path : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/location/osm/src/ |
Current File : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/location/osm/src/mapservice.js |
import { Event, Tag, Text } from 'main.core'; import { Location, MapBase, ControlMode, Point, ErrorPublisher, } from 'location.core'; import { Leaflet } from '../leaflet/src/leaflet'; import OSM from './osm'; import './css/mapservice.css'; /** * Class for the autocomplete locations and addresses inputs */ export default class MapService extends MapBase { static #onChangedEvent = 'onChanged'; static #onStartChanging = 'onStartChanging'; static #onEndChanging = 'onEndChanging'; static #onMapViewChanged = 'onMapViewChanged'; /** {number} */ #zoom; /** {ControlMode} */ #mode; /** {?Location} */ #location; /** {String} */ #languageId; /** {String} */ #sourceLanguageId; /** {Leaflet.map} */ #map; /** {Leaflet.marker} */ #marker; /** {GeocodingService} */ #geocodingService; #isUpdating = false; #timerId; #changeDelay = 700; #tileUrlTemplate; #attribution = '<a href="https://leafletjs.com" title="A JS library for interactive maps" target="_blank">Leaflet</a> | Map data © <a href="https://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> contributors'; #mapFactoryMethod; #markerFactoryMethod; #tileLayerFactoryMethod; #locationRepository; #isResizeInvalidated = false; constructor(props) { super(props); this.#languageId = props.languageId; this.#sourceLanguageId = props.sourceLanguageId; this.#geocodingService = props.geocodingService; this.#mapFactoryMethod = props.mapFactoryMethod; this.#markerFactoryMethod = props.markerFactoryMethod; this.#tileLayerFactoryMethod = props.tileLayerFactoryMethod; this.#tileUrlTemplate = `${props.mapServiceUrl}/hot/en/{z}/{x}/{y}.png`; this.#locationRepository = props.locationRepository; } set mode(mode: string): void { this.#mode = mode; if (this.#marker) { this.#marker.draggable = mode === ControlMode.edit; } } get map(): ?Object { return this.#map; } set map(map) { this.#map = map; } get mode(): string { return this.#mode; } get marker(): ?Object { return this.#marker; } set marker(marker) { this.#marker = marker; } get zoom(): number { return this.#zoom; } set zoom(zoom: number): void { this.#zoom = zoom; if (this.#map) { this.#map.setZoom(zoom); } } set location(location: ?Location): void { this.#location = location; if (location) { if (this.#marker) { this.#isUpdating = true; this.#marker.setLatLng([location.latitude, location.longitude]); this.#isUpdating = false; } if (this.#map) { if (!this.#map.hasLayer(this.#marker)) { this.#marker.addTo(this.#map); } this.#map.panTo([location.latitude, location.longitude]); } } else if (this.#marker) { this.#marker.remove(); } this.#adjustZoom(); } #adjustZoom(): void { if (!this.#location) { return; } const zoom = MapService.getZoomByLocation(this.#location); if (zoom !== null && zoom !== this.#zoom) { this.zoom = zoom; } } get location(): Location { return this.#location; } onLocationChangedEventSubscribe(listener: function): void { this.subscribe(MapService.#onChangedEvent, listener); } onStartChangingSubscribe(listener: function): void { this.subscribe(MapService.#onStartChanging, listener); } onEndChangingSubscribe(listener: function): void { this.subscribe(MapService.#onEndChanging, listener); } onMapViewChangedSubscribe(listener: function): void { this.subscribe(MapService.#onMapViewChanged, listener); } #onMapClick(lat: string, lng: string): void { if (this.#mode === ControlMode.edit) { if (!this.#map.hasLayer(this.#marker)) { this.#marker.addTo(this.#map); } this.#marker.setLatLng([lat, lng]); this.#createTimer(lat, lng); } } #createTimer(lat: string, lng: string): void { if (this.#timerId !== null) { clearTimeout(this.#timerId); } this.#timerId = setTimeout(() => { const requestId = Text.getRandom(); this.emit(MapService.#onStartChanging, { requestId }); this.#timerId = null; this.#map.panTo([lat, lng]); const point = new Point(lat, lng); this.#geocodingService.reverse(point, this.#getReverseZoom()) .then( (location) => { let result; if (location) { result = this.#locationRepository.findByExternalId( location.externalId, OSM.code, this.#languageId ).then((foundLocation) => { /** * Use marker coordinates */ if (foundLocation) { foundLocation.longitude = point.longitude; if (foundLocation.address) { foundLocation.address.longitude = point.longitude; } foundLocation.latitude = point.latitude; if (foundLocation.address) { foundLocation.address.latitude = point.latitude; } } return foundLocation; }); } else { result = new Promise((resolve) => { resolve(null); }); } return result; } ) .then((location) => { this.emit(MapService.#onEndChanging, { requestId }); this.#emitOnLocationChangedEvent(location); }) .catch((response) => { this.emit(MapService.#onEndChanging, { requestId }); ErrorPublisher.getInstance().notify(response.errors); }); }, this.#changeDelay ); } #getReverseZoom(): number { return this.#zoom >= 15 ? 18 : this.#zoom; } #emitOnLocationChangedEvent(location: ?Location): void { if (this.#mode === ControlMode.edit) { this.emit(MapService.#onChangedEvent, { location: location }); } } #onMarkerUpdatePosition(lat: string, lng: string): void { if (!this.#isUpdating && this.#mode === ControlMode.edit) { this.#createTimer(lat, lng); } } render(props: Object): Promise { this.#mode = props.mode; this.#location = props.location || null; const container = Tag.render`<div class="location-osm-map-container"></div>`; props.mapContainer.appendChild(container); return new Promise((resolve) => { this.#map = this.#mapFactoryMethod(container, { attributionControl: false, zoomControl: BX.prop.getBoolean(props, 'zoomControl', true), }); this.#map.on('load', () => { resolve(); }); this.#map.on('click', (e) => { this.#onMapClick(e.latlng.lat, e.latlng.lng); }); window.addEventListener('resize', (event) => { this.#isResizeInvalidated = true; this.#invalidateMapSize(); }); this.#marker = this.#markerFactoryMethod( [this.#location.latitude, this.#location.longitude], { draggable: this.#mode === ControlMode.edit, autoPan: true } ); this.#marker.addTo(this.#map); this.#marker.on('move', (e) => { this.#onMarkerUpdatePosition(e.latlng.lat, e.latlng.lng); }); this.#map.setView( [this.#location.latitude, this.#location.longitude], MapService.getZoomByLocation(this.#location) ); const tile = this.#tileLayerFactoryMethod.call(); tile.initialize(this.#tileUrlTemplate, { /** * In order to avoid blurry tiles on retina screens, we need to apply the below options: * detectRetina: true, * maxNativeZoom: 22, * maxZoom: 18, * * but we can't do it right now because of the following bug in the leaflet library: * https://github.com/Leaflet/Leaflet/issues/8850 * which causes fetching non-existent tiles (19, 20, etc. zoom levels) */ maxZoom: 18, }); tile.addTo(this.#map); this.#map.on('zoomend', () => { this.#zoom = this.#map.getZoom(); }); const attribution = new Leaflet.Control.Attribution(); attribution.setPrefix(''); attribution.addAttribution(this.#attribution); this.#map.addControl(attribution); }); } #invalidateMapSize() { setTimeout(() => { this.#map.invalidateSize(); }, 10); } onMapShow() { if (this.#isResizeInvalidated) { this.#isResizeInvalidated = false; this.#invalidateMapSize(); } } destroy() { Event.unbindAll(this); this.#map.remove(); this.#marker.remove(); super.destroy(); } }