import {BehaviorSubject} from "rxjs"; import {distanceInM} from "../utils"; import {environment} from "../../environments/environment"; import {LatLng} from "../models/latlng"; declare const L; export enum MapLayers { ESRI_TOPOGRAPHIC, ESRI_IMAGERY, ESRI_IMAGERY_CLARITY } export enum WeatherLayers { CLOUDS_NEW, PRECIPITATION_NEW, SEA_LEVEL_PRESSURE, WIND_NEW, TEMP_NEW } export const ARROW = L.icon({iconUrl: '/assets/images/arrow.png', iconSize: [40, 45], iconAnchor: [20, 23]}); export const MARKER = L.icon({iconUrl: '/assets/images/marker.png', iconSize: [40, 55], iconAnchor: [20, 55]}); export class MapService { private drawListener; private markers = []; private measurements = []; private mapLayer; private weatherLayer; click = new BehaviorSubject<{lat: number, lng: number}>(null); deleteMode = false; drawingColor = '#ff4141'; drawingWeight = 10; map; constructor(private elementId: string) { this.map = L.map(elementId, {attributionControl: false, tap: true, zoomControl: false, maxBoundsViscosity: 1, doubleClickZoom: false}).setView({lat: 0, lng: 0}, 10); this.map.on('click', (e) => this.click.next(e.latlng)); this.setMapLayer(); } centerOn(latlng: LatLng, zoom=14) { this.map.setView(latlng, zoom); } delete(...symbols) { symbols.forEach(s => { this.map.removeLayer(s); this.markers = this.markers.filter(m => m != s); this.measurements = this.markers.filter(m => m != s); }); } deleteAll() { this.markers.forEach(m => this.delete(m)); this.measurements.forEach(m => this.delete(m.line, m.decoration)); } lock(unlock?: boolean) { if(unlock) { this.map.setMaxBounds(null); this.map.boxZoom.disable(); this.map.touchZoom.enable(); this.map.scrollWheelZoom.enable(); } else { this.map.setMaxBounds(this.map.getBounds()); this.map.boxZoom.disable(); this.map.touchZoom.disable(); this.map.scrollWheelZoom.disable(); } } setMapLayer(layer?: MapLayers) { if(this.mapLayer) this.map.removeLayer(this.mapLayer); switch(layer) { case MapLayers.ESRI_TOPOGRAPHIC: this.mapLayer = L.esri.basemapLayer('Topographic'); break; case MapLayers.ESRI_IMAGERY: this.mapLayer = L.esri.basemapLayer('Imagery'); break; default: this.mapLayer = L.esri.basemapLayer('ImageryClarity'); } this.mapLayer.addTo(this.map); if(this.weatherLayer) this.setWeatherLayer(this.weatherLayer.name); } setWeatherLayer(layer?: WeatherLayers) { if(this.weatherLayer) { this.map.removeLayer(this.weatherLayer.layer); this.weatherLayer = null; } switch(layer) { case WeatherLayers.CLOUDS_NEW: this.weatherLayer = {name: WeatherLayers.CLOUDS_NEW, layer: L.OWM.clouds({appId: environment.openWeather, opacity: 0.5})}; break; case WeatherLayers.PRECIPITATION_NEW: this.weatherLayer = {name: WeatherLayers.PRECIPITATION_NEW, layer: L.OWM.precipitation({appId: environment.openWeather, opacity: 0.5})}; break; case WeatherLayers.SEA_LEVEL_PRESSURE: this.weatherLayer = {name: WeatherLayers.SEA_LEVEL_PRESSURE, layer: L.OWM.pressure({appId: environment.openWeather, opacity: 0.5})}; break; case WeatherLayers.WIND_NEW: this.weatherLayer = {name: WeatherLayers.WIND_NEW, layer: L.OWM.wind({appId: environment.openWeather, opacity: 0.5})}; break; case WeatherLayers.TEMP_NEW: this.weatherLayer = {name: WeatherLayers.TEMP_NEW, layer: L.OWM.temperature({appId: environment.openWeather, opacity: 0.5})}; break; } if(this.weatherLayer) this.weatherLayer.layer.addTo(this.map); } newCircle(latlng: LatLng, radius: number, opts: any={}) { opts.radius = radius; let circle = L.circle(latlng, opts).addTo(this.map); circle.on('click', () => { if(!opts.noDelete && this.deleteMode) { this.delete(circle); } else { } }); return circle; } newMarker(latlng: LatLng, opts: any={}) { if(!opts.icon) opts.icon = MARKER; let marker = L.marker(latlng, opts).addTo(this.map); this.markers.push(marker); marker.on('click', () => { if(!opts.noDelete && this.deleteMode) { this.delete(marker); } else { } }); return marker; } newMeasurement(latlng1: LatLng, latlng2: LatLng) { let line = L.polyline([latlng1, latlng2], {color: '#ff4141', weight: 5}).addTo(this.map); let decoration = L.polylineDecorator(line, {patterns: [ {offset: '100%', repeat: 0, symbol: L.Symbol.arrowHead({pixelSize: 15, polygon: false, headAngle: 180, pathOptions: {color: '#ff4141', stroke: true}})}, {offset: '-100%', repeat: 0, symbol: L.Symbol.arrowHead({pixelSize: 15, polygon: false, headAngle: 180, pathOptions: {color: '#ff4141', stroke: true}})} ]}).addTo(this.map); this.measurements.push({line: line, decoration: decoration}); let distance = distanceInM(latlng1.lat, latlng1.lng, latlng2.lat, latlng2.lng); line.bindPopup(`${distance > 1000 ? Math.round(distance / 100) / 10 : Math.round(distance)} ${distance > 1000 ? 'k' : ''}m`, {autoClose: false, closeOnClick: false}).openPopup(); line.on('click', () => { if(this.deleteMode) this.delete(line, decoration);}); return {line: line, decoration: decoration}; } startDrawing() { this.lock(); let poly; this.drawListener = e => { poly = L.polyline([e.latlng], {interactive: true, color: this.drawingColor, weight: this.drawingWeight}).addTo(this.map); poly.on('click', () => { if(this.deleteMode) this.map.removeLayer(poly); }); let pushLine = e => poly.addLatLng(e.latlng); this.map.on('mousemove touchmove', pushLine); this.map.on('mouseup touchend', () => this.map.off('mousemove touchmove', pushLine)); }; this.map.on('mousedown touchstart', this.drawListener); } stopDrawing() { this.lock(true); this.map.setMaxBounds(null); this.map.off('mousedown touchstart', this.drawListener); } }