map-alliance/src/app/services/map.service.ts
2019-09-02 18:05:11 -04:00

199 lines
8.7 KiB
TypeScript

import {BehaviorSubject} from "rxjs";
import {latLngDistance} from "../utils";
import {environment} from "../../environments/environment";
import {Circle, LatLng, MapSymbol, Marker, Measurement, Polygon, Polyline, Rectangle} from "../models/mapSymbol";
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
}
const ARROW = L.icon({iconUrl: '/assets/images/arrow.png', iconSize: [40, 45], iconAnchor: [20, 23]});
const DOT = L.icon({iconUrl: '/assets/images/dot.png', iconSize: [25, 25], iconAnchor: [13, 13]});
const MARKER = L.icon({iconUrl: '/assets/images/marker.png', iconSize: [40, 55], iconAnchor: [20, 55]});
const MEASURE = L.icon({iconUrl: '/assets/images/measure.png', iconSize: [75, 50], iconAnchor: [25, 25]});
export class MapService {
private readonly map;
private circles = [];
private markers = [];
private measurements = [];
private mapLayer;
private polygons = [];
private polylines = [];
private rectangles = [];
private weatherLayer;
click = new BehaviorSubject<{latlng: LatLng, symbol?: MapSymbol, item?: any}>(null);
touch = new BehaviorSubject<{type: string, latlng: LatLng}>(null);
constructor(private elementId: string) {
this.map = L.map(elementId, {attributionControl: false, editable: true, tap: true, zoomControl: false, maxBoundsViscosity: 1, doubleClickZoom: false}).setView({lat: 0, lng: 0}, 10);
this.map.on('click', (e) => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}}));
this.map.on('touchstart', (e) => this.touch.next({type: 'start', latlng: {lat: e.latlng.lat, lng: e.latlng.lng}}));
this.map.on('touchmove', (e) => this.touch.next({type: 'move', latlng: {lat: e.latlng.lat, lng: e.latlng.lng}}));
this.map.on('touchend', (e) => this.touch.next({type: 'end', latlng: {lat: e.latlng.lat, lng: e.latlng.lng}}));
this.setMapLayer();
}
private getIcon(name: string) {
switch(name) {
case 'arrow':
return ARROW;
case 'dot':
return DOT;
case 'measure':
return MEASURE;
default:
return MARKER;
}
}
centerOn(latlng: LatLng, zoom=14) {
this.map.setView(latlng, zoom);
}
delete(...symbols) {
symbols.forEach(s => {
this.map.removeLayer(s);
this.circles = this.circles.filter(c => c != c);
this.markers = this.markers.filter(m => m != s);
this.measurements = this.measurements.filter(m => m != s);
this.polygons = this.polygons.filter(p => p != s);
this.polylines = this.polylines.filter(p => p != s);
this.rectangles = this.rectangles.filter(r => r != s);
});
}
deleteAll() {
this.circles.forEach(c => this.delete(c));
this.markers.forEach(m => this.delete(m));
this.measurements.forEach(m => this.delete(m));
this.polygons.forEach(p => this.delete(p));
this.polylines.forEach(p => this.delete(p));
this.rectangles.forEach(r => this.delete(r));
}
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();
}
}
newCircle(c: Circle) {
let circle = L.circle(c.latlng, Object.assign({color: '#ff4141'}, c)).addTo(this.map);
if(c.label) circle.bindTooltip(c.label, {permanent: true, direction: 'center'});
circle.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: c, item: circle}));
if(!c.noDelete) this.circles.push(circle);
return circle;
}
newMarker(m: Marker) {
let marker = L.marker(m.latlng, Object.assign({color: '#ff4141'}, m, {icon: m.icon ? this.getIcon(m.icon) : MARKER})).addTo(this.map);
if(m.label) marker.bindTooltip(m.label, {permanent: true, direction: 'bottom'});
marker.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: m, item: marker}));
if(!m.noDelete) this.markers.push(marker);
return marker;
}
newMeasurement(m: Measurement) {
let line = L.polyline([m.latlng, m.latlng2], Object.assign({color: '#ff4141', weight: 8}, m));
let decoration = L.polylineDecorator(line, {patterns: [
{offset: '100%', repeat: 0, symbol: L.Symbol.arrowHead({pixelSize: 10, polygon: false, headAngle: 180, pathOptions: Object.assign({color: '#ff4141', weight: 8}, m)})},
{offset: '-100%', repeat: 0, symbol: L.Symbol.arrowHead({pixelSize: 10, polygon: false, headAngle: 180, pathOptions: Object.assign({color: '#ff4141', weight: 8}, m)})}
]});
let group = new L.LayerGroup([line, decoration]).addTo(this.map);
if(!m.noDelete) this.measurements.push(group);
let distance = latLngDistance(m.latlng, m.latlng2);
line.bindPopup(`${distance > 1000 ? Math.round(distance / 100) / 10 : Math.round(distance)} ${distance > 1000 ? 'k' : ''}m`, {autoClose: false, closeOnClick: false}).openPopup();
line.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: m, item: group}));
return group;
}
newPolygon(p: Polygon) {
let polygon = new L.Polygon(p.latlng, Object.assign({color: '#ff4141'}, p)).addTo(this.map);
polygon.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: p, item: polygon}));
if(!p.noDelete) this.polygons.push(polygon);
return polygon;
}
newPolyline(p: Polyline) {
let polyline = new L.Polyline(p.latlng, Object.assign({color: '#ff4141', weight: 10}, p)).addTo(this.map);
polyline.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: p, item: polyline}));
if(!p.noDelete) this.polylines.push(polyline);
return polyline;
}
newRectangle(r: Rectangle) {
let rect = new L.Rectangle([r.latlng, r.latlng2], Object.assign({color: '#ff4141'}, r)).addTo(this.map);
if(r.label) rect.bindTooltip(r.label, {permanent: true, direction: 'center'});
rect.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: r, item: rect}));
if(!r.noDelete) this.rectangles.push(rect);
return rect;
}
setMapLayer(layer?: MapLayers) {
if(this.mapLayer) this.map.removeLayer(this.mapLayer);
if(layer == null) layer = MapLayers.ESRI_IMAGERY;
switch(layer) {
case MapLayers.ESRI_TOPOGRAPHIC:
this.mapLayer = L.esri.basemapLayer('Topographic');
break;
case MapLayers.ESRI_IMAGERY:
this.mapLayer = L.esri.basemapLayer('Imagery');
break;
case MapLayers.ESRI_IMAGERY_CLARITY:
this.mapLayer = L.esri.basemapLayer('ImageryClarity');
break;
}
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);
}
}