map-alliance/src/app/services/map.service.ts

176 lines
6.6 KiB
TypeScript

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);
}
}