This commit is contained in:
Zakary Timson 2019-08-22 22:29:42 -04:00
parent 930bbd15a9
commit 88cfd67184
8 changed files with 224 additions and 155 deletions

View File

@ -34,6 +34,8 @@
], ],
"scripts": [ "scripts": [
"./node_modules/leaflet/dist/leaflet.js", "./node_modules/leaflet/dist/leaflet.js",
"./node_modules/leaflet-polylinedecorator/dist/leaflet.polylineDecorator.js",
"./node_modules/leaflet-openweathermap/leaflet-openweathermap.js",
"./node_modules/esri-leaflet/dist/esri-leaflet.js" "./node_modules/esri-leaflet/dist/esri-leaflet.js"
] ]
}, },

View File

@ -32,6 +32,8 @@
"firebase": "^6.3.0", "firebase": "^6.3.0",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"leaflet": "^1.5.1", "leaflet": "^1.5.1",
"leaflet-openweathermap": "^1.0.0",
"leaflet-polylinedecorator": "^1.6.0",
"rxjs": "~6.4.0", "rxjs": "~6.4.0",
"tslib": "^1.9.0", "tslib": "^1.9.0",
"zone.js": "~0.9.1" "zone.js": "~0.9.1"

View File

@ -0,0 +1,133 @@
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 class MapService {
private drawListener;
private markers = [];
private measurements = [];
private mapLayer;
private weatherLayer;
click = new BehaviorSubject<{lat: number, lng: number}>(null);
deleteMode = false;
drawingColor = '#d82b00';
drawingWeight = 10;
map;
constructor(private elementId: string) {
this.map = L.map(elementId, {attributionControl: false, zoomControl: 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));
}
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);
}
setWeatherLayer(layer?: WeatherLayers) {
if(this.weatherLayer) this.map.removeLayer(this.weatherLayer);
switch(layer) {
case WeatherLayers.CLOUDS_NEW:
this.weatherLayer = L.OWM.clouds({appId: environment.openWeather, opacity: 0.5});
break;
case WeatherLayers.PRECIPITATION_NEW:
this.weatherLayer = L.OWM.precipitation({appId: environment.openWeather, opacity: 0.5});
break;
case WeatherLayers.SEA_LEVEL_PRESSURE:
this.weatherLayer = L.OWM.pressure({appId: environment.openWeather, opacity: 0.5});
break;
case WeatherLayers.WIND_NEW:
this.weatherLayer = L.OWM.wind({appId: environment.openWeather, opacity: 0.5});
break;
case WeatherLayers.TEMP_NEW:
this.weatherLayer = L.OWM.temperature({appId: environment.openWeather, opacity: 0.5});
break;
}
if(this.weatherLayer) this.weatherLayer.addTo(this.map);
}
newMarker(latlng: LatLng) {
let marker = L.marker(latlng).addTo(this.map);
this.markers.push(marker);
marker.on('click', () => { if(this.deleteMode) this.delete(marker); });
return marker;
}
newMeasurement(latlng1: LatLng, latlng2: LatLng) {
let line = L.polyline([latlng1, latlng2], {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: {stroke: true}})},
{offset: '-100%', repeat: 0, symbol: L.Symbol.arrowHead({pixelSize: 15, polygon: false, headAngle: 180, pathOptions: {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' : null}m`).openPopup();
line.on('click', () => { if(this.deleteMode) this.delete(line, decoration);});
return {line: line, decoration: decoration};
}
startDrawing() {
this.map.dragging.disable();
this.drawListener = () => {
let poly = L.polyline([], {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', pushLine);
this.map.on('mouseup', () => this.map.off('mousemove', pushLine));
};
this.map.on('mousedown', this.drawListener);
}
stopDrawing() {
this.map.dragging.enable();
this.map.off('mousedown', this.drawListener);
}
}

16
src/app/utils.ts Normal file
View File

@ -0,0 +1,16 @@
export function deg2rad(deg) {
return deg * (Math.PI/180);
}
export function distanceInM(lat1: number, lon1: number, lat2: number, lon2: number) {
const R = 6371; // Radius of the earth in km
let dLat = deg2rad(lat2-lat1); // deg2rad below
let dLon = deg2rad(lon2-lon1);
let a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2)
;
let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c * 1000
}

View File

@ -1,7 +1,7 @@
<toolbar [(menu)]="menu"></toolbar> <toolbar [(menu)]="menu"></toolbar>
<div id="map"></div> <div id="map"></div>
<div *ngIf="showPalette" [@flyInRight] [@flyOutRight] class="palette"> <div *ngIf="showPalette" [@flyInRight] [@flyOutRight] class="palette">
<palette [(selected)]="drawColor"></palette> <palette [(selected)]="drawColor" (selectedChange)="map.drawingColor = $event"></palette>
</div> </div>
<div class="info p-2"> <div class="info p-2">
<span *ngIf="!position" class="text-danger">No GPS</span> <span *ngIf="!position" class="text-danger">No GPS</span>

View File

@ -4,9 +4,8 @@ import {filter, skip, take} from "rxjs/operators";
import {MatBottomSheet, MatSnackBar} from "@angular/material"; import {MatBottomSheet, MatSnackBar} from "@angular/material";
import {CalibrateComponent} from "../../components/calibrate/calibrate.component"; import {CalibrateComponent} from "../../components/calibrate/calibrate.component";
import {ToolbarItem} from "../../components/toolbar/toolbarItem"; import {ToolbarItem} from "../../components/toolbar/toolbarItem";
import {BehaviorSubject} from "rxjs";
import {LatLngLiteral} from "@agm/core";
import {flyInRight, flyOutRight} from "../../animations"; import {flyInRight, flyOutRight} from "../../animations";
import {MapService} from "../../services/map.service";
declare const L; declare const L;
@ -14,185 +13,100 @@ declare const L;
selector: 'map', selector: 'map',
templateUrl: 'map.component.html', templateUrl: 'map.component.html',
styleUrls: ['map.component.scss'], styleUrls: ['map.component.scss'],
providers: [MapService],
animations: [flyInRight, flyOutRight] animations: [flyInRight, flyOutRight]
}) })
export class MapComponent implements OnInit { export class MapComponent implements OnInit {
map; map: MapService;
markers = [];
position;
drawColor = '#d82b00';
drawColor: string;
drawListener = [];
mapApi: any;
mapClick = new BehaviorSubject<LatLngLiteral>(null);
mapStyle = [{
"featureType": "poi",
"elementType": "labels",
"stylers": [{
"visibility": "off"
}]
}];
position: any;
showPalette = false; showPalette = false;
style = 'terrain';
isNaN = isNaN; isNaN = isNaN;
menu: ToolbarItem[][] = [[ menu: ToolbarItem[][] = [[
{name: 'compass', icon: 'explore', hidden: true}, {name: 'compass', icon: 'explore', hidden: true},
], [ ], [
{name: 'marker', icon: 'room', toggle: true, individualToggle: true}, {name: 'marker', icon: 'room', toggle: true, individualToggle: true, click: () => this.addMarker()},
{name: 'draw', icon: 'create', toggle: true, individualToggle: true}, {name: 'draw', icon: 'create', toggle: true, individualToggle: true, onEnabled: () => this.startDrawing(), onDisabled: () => this.endDrawing()},
{name: 'measure', icon: 'straighten', toggle: true, individualToggle: true}, {name: 'measure', icon: 'straighten', toggle: true, individualToggle: true, click: () => this.measure()},
{name: 'delete', icon: 'delete', toggle: true, individualToggle: true}, {name: 'delete', icon: 'delete', toggle: true, individualToggle: true, onEnabled: () => this.map.deleteMode = true, onDisabled: () => this.map.deleteMode = false},
{name: 'style', icon: 'terrain', enabled: true, toggle: true}, {name: 'style', icon: 'terrain', enabled: true, toggle: true},
{name: 'compass', icon: 'explore'} {name: 'weather', icon: 'cloud', toggle: true},
{name: 'compass', icon: 'explore', click: () => this.calibrate()}
], [ ], [
{name: 'messages', icon: 'chat', hidden: true}, {name: 'messages', icon: 'chat', hidden: true},
{name: 'identity', icon: 'perm_identity', hidden: true}, {name: 'identity', icon: 'perm_identity', hidden: true},
{name: 'settings', icon: 'settings', hidden: true} {name: 'settings', icon: 'settings', hidden: true}
]]; ]];
constructor(public physicsService: PhysicsService, private snackBar: MatSnackBar, private bottomSheet: MatBottomSheet) { constructor(public physicsService: PhysicsService, private snackBar: MatSnackBar, private bottomSheet: MatBottomSheet) { }
physicsService.info.pipe(filter(coord => !!coord)).subscribe(pos => {
if(this.mapApi) {
// if(!this.position) this.center(pos);
this.position = pos;
ngOnInit() {
this.map = new MapService('map');
this.physicsService.info.pipe(filter(coord => !!coord)).subscribe(pos => {
if(!this.position) this.center({lat: pos.latitude, lng: pos.longitude});
this.position = pos;
if(this.position.heading != null) { if(this.position.heading != null) {
let marker: HTMLElement = document.querySelector('img[src*="arrow.png"]'); let marker: HTMLElement = document.querySelector('img[src*="arrow.png"]');
if(marker) marker.style.transform = `rotate(${this.position.heading}deg)` if(marker) marker.style.transform = `rotate(${this.position.heading}deg)`
} }
}
}); });
physicsService.requireCalibration.subscribe(() => { this.physicsService.requireCalibration.subscribe(() => {
snackBar.open('Compass requires calibration', 'calibrate', { this.snackBar.open('Compass requires calibration', 'calibrate', {
duration: 5000, duration: 5000,
panelClass: 'bg-warning,text-white' panelClass: 'bg-warning,text-white'
}).onAction()/*.subscribe(() => this.calibrate());*/ }).onAction().subscribe(() => this.calibrate());
}); });
} }
ngOnInit() { center(pos?) {
this.map = L.map('map', {attributionControl: false, zoomControl: false}).setView([37.75, -122.23], 10); if(!pos) pos = {lat: this.position.latitude, lng: this.position.longitude};
L.esri.basemapLayer('ImageryClarity').addTo(this.map); this.map.centerOn(pos);
} }
// addMarker() { addMarker() {
// this.mapClick.pipe(skip(1), take(1)).subscribe(coords => { this.map.click.pipe(skip(1), take(1)).subscribe(latlng => {
// this.menu[1][0].enabled = false; this.menu[1][0].enabled = false;
// let marker = new google.maps.Marker({ this.markers.push(latlng);
// map: this.mapApi, this.map.newMarker(latlng);
// position: {lat: coords.lat, lng: coords.lng} });
// }); }
// google.maps.event.addListener(marker, 'click', () => {
// if(this.menu[1][3].enabled) marker.setMap(null) draw() {
// }); this.markers.forEach(marker => this.map.newMarker(marker));
// }); }
// }
// measure() {
// measure() { let firstPoint;
// let deg2rad = (deg) => deg * (Math.PI/180); this.map.click.pipe(skip(1), take(2)).subscribe(latlng => {
// if(!firstPoint) {
// let distanceInM = (lat1, lon1, lat2, lon2) => { firstPoint = this.map.newMarker(latlng);
// const R = 6371; // Radius of the earth in km } else {
// let dLat = deg2rad(lat2-lat1); // deg2rad below this.menu[1][2].enabled = false;
// let dLon = deg2rad(lon2-lon1); this.map.newMeasurement(firstPoint.getLatLng(), latlng);
// let a = this.map.delete(firstPoint);
// Math.sin(dLat/2) * Math.sin(dLat/2) + }
// Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * })
// Math.sin(dLon/2) * Math.sin(dLon/2) }
// ;
// let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); calibrate() {
// return R * c * 1000 this.bottomSheet.open(CalibrateComponent, {
// }; hasBackdrop: false,
// disableClose: true
// let first; });
// this.mapClick.pipe(skip(1), take(2)).subscribe(coords => { }
// if(!first) {
// first = coords; startDrawing() {
// } else { this.showPalette = true;
// this.menu[1][2].enabled = false; this.map.startDrawing();
// }
// let line = new google.maps.Polyline({
// map: this.mapApi, endDrawing() {
// path: [first, coords], this.showPalette = false;
// strokeColor: '#f00', this.map.stopDrawing()
// icons: [{ }
// icon: {path: google.maps.SymbolPath.BACKWARD_CLOSED_ARROW},
// offset: '0%'
// }, {
// icon: {path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW},
// offset: '100%'
// }]
// });
//
// let distance = distanceInM(first.lat, first.lng, coords.lat, coords.lng);
//
// let info = new google.maps.InfoWindow({
// content: distance >= 1000 ? `${Math.round(distance / 100) / 10} km` : `${Math.round(distance)} m`,
// position: {lat: (first.lat + coords.lat) / 2, lng: (first.lng + coords.lng) / 2}
// });
// info.open(this.mapApi);
//
// google.maps.event.addListener(line, 'click', () => {
// if(this.menu[1][3].enabled) {
// line.setMap(null);
// info.setMap(null);
// }
// });
//
// google.maps.event.addListener(info, 'click', () => {
// if(this.menu[1][3].enabled) {
// line.setMap(null);
// info.setMap(null);
// }
// });
// }
// })
// }
//
// calibrate() {
// this.bottomSheet.open(CalibrateComponent, {
// hasBackdrop: false,
// disableClose: true
// });
// }
//
// center(pos?) {
// if(!pos) pos = this.position;
// this.mapApi.setCenter({lat: pos.latitude, lng: pos.longitude});
// }
//
// startDraw() {
// this.showPalette = true;
// this.mapApi.setOptions({draggable: false});
//
// let drawHander = () => {
// let poly = new google.maps.Polyline({map: this.mapApi, clickable: true, strokeColor: this.drawColor});
// google.maps.event.addListener(poly, 'click', () => {
// if(this.menu[1][3].enabled) poly.setMap(null);
// });
// let moveListener = [
// google.maps.event.addListener(this.mapApi, 'touchmove', e => poly.getPath().push(e.latLng)),
// google.maps.event.addListener(this.mapApi, 'mousemove', e => poly.getPath().push(e.latLng))
// ];
// google.maps.event.addListener(this.mapApi, 'touchend', () => moveListener.forEach(listener => google.maps.event.removeListener(listener)));
// google.maps.event.addListener(this.mapApi, 'mouseup', () => moveListener.forEach(listener => google.maps.event.removeListener(listener)));
// google.maps.event.addListener(poly, 'touchend', () => moveListener.forEach(listener => google.maps.event.removeListener(listener)));
// google.maps.event.addListener(poly, 'mouseup', () => moveListener.forEach(listener => google.maps.event.removeListener(listener)));
// };
//
// this.drawListener = [
// google.maps.event.addDomListener(this.mapApi.getDiv(), 'touchstart', drawHander),
// google.maps.event.addDomListener(this.mapApi.getDiv(), 'mousedown', drawHander)
// ];
// }
//
// endDraw() {
// this.showPalette = false;
// this.mapApi.setOptions({draggable: true});
// this.drawListener.forEach(listener => google.maps.event.removeListener(listener));
// this.drawListener = [];
// }
} }

View File

@ -8,5 +8,6 @@ export const environment = {
messagingSenderId: "443222036375", messagingSenderId: "443222036375",
appId: "1:443222036375:web:2e2906e3ca49aa63" appId: "1:443222036375:web:2e2906e3ca49aa63"
}, },
openWeather: 'e8391af54b6fc09dc82b019fc68b8409',
production: true production: true
}; };

View File

@ -12,6 +12,7 @@ export const environment = {
messagingSenderId: "443222036375", messagingSenderId: "443222036375",
appId: "1:443222036375:web:2e2906e3ca49aa63" appId: "1:443222036375:web:2e2906e3ca49aa63"
}, },
openWeather: 'e8391af54b6fc09dc82b019fc68b8409',
production: false production: false
}; };