Reorganized

This commit is contained in:
ztimson
2019-07-22 11:59:30 -04:00
parent 0ca0894db5
commit ba84322719
21 changed files with 945 additions and 59 deletions

View File

@ -0,0 +1,32 @@
<toolbar [(menu)]="menu"></toolbar>
<agm-map class="map" [mapTypeId]="style" [zoomControl]="false" [streetViewControl]="false" [disableDoubleClickZoom]="true" (mapReady)="mapApi = $event" gestureHandling="greedy" (mapClick)="mapClick.next($event.coords)">
<ng-container *ngIf="position">
<agm-marker *ngIf="position.heading == null" [markerClickable]="false" [latitude]="position.latitude" [longitude]="position.longitude" [iconUrl]="{url: '/assets/images/dot.png', anchor: {x: 11, y: 10}}"></agm-marker>
<agm-marker *ngIf="position.heading != null" [markerClickable]="false" [latitude]="position.latitude" [longitude]="position.longitude" [iconUrl]="{url: '/assets/images/arrow.png', anchor: {x: 11, y: 13}, rotation: 90}"></agm-marker>
<agm-circle [latitude]="position.latitude" [longitude]="position.longitude" [radius]="position.accuracy" fillColor="#5C95F2" [clickable]="false"></agm-circle>
</ng-container>
</agm-map>
<div class="palette">
<palette></palette>
</div>
<div class="info p-2">
<span *ngIf="!position" class="text-danger">No GPS</span>
<div *ngIf="position" class="text-white">
Heading:
<span *ngIf="position.heading == null" class="text-danger">No Heading</span>
<span *ngIf="!isNaN(position.heading)">{{position.heading | number : '1.0-0'}}°</span>
<br>
Latitude: {{position.latitude | number : '0.0-5'}}
<br>
Longitude: {{position.longitude | number : '0.0-5'}}
<br>
Altitude:
<span *ngIf="!position.altitude" class="text-danger">No Altitude</span>
<span *ngIf="position.altitude">{{position.altitude | number : '0.0-0'}} m</span>
<br>
Speed:
<span *ngIf="!position.speed" class="text-danger">No Speed</span>
<span *ngIf="position.speed">{{position.speed * 60 * 60 / 1000 | number : '1.0-1'}} km/h</span>
</div>
</div>
<button *ngIf="position" mat-fab class="gps" (click)="center()"><mat-icon>gps_fixed</mat-icon></button>

View File

@ -0,0 +1,18 @@
.map {
height: calc(100vh - 40px);
}
.info {
background-color: #00000050;
position: fixed;
z-index: 100;
bottom: 15px;
left: 15px;
}
.gps {
position: fixed;
z-index: 100;
bottom: 15px;
right: 15px;
}

View File

@ -0,0 +1,177 @@
import {Component} from "@angular/core";
import {PhysicsService} from "../../services/physics/physics.service";
import {filter, skip, take} from "rxjs/operators";
import {MatBottomSheet, MatSnackBar} from "@angular/material";
import {CalibrateComponent} from "../../components/calibrate/calibrate.component";
import {ToolbarItem} from "../../components/toolbar/toolbarItem";
import {BehaviorSubject} from "rxjs";
import {LatLngLiteral} from "@agm/core";
declare const google;
@Component({
selector: 'map',
templateUrl: 'map.component.html',
styleUrls: ['map.component.scss']
})
export class MapComponent {
drawListener = [];
mapApi: any;
mapClick = new BehaviorSubject<LatLngLiteral>(null);
position: any;
style = 'terrain';
isNaN = isNaN;
menu: ToolbarItem[][] = [[
{name: 'compass', icon: 'explore', click: () => this.calibrate(), hidden: true},
], [
{name: 'marker', icon: 'room', toggle: true, individualToggle: true, click: () => this.addMarker()},
{name: 'draw', icon: 'create', toggle: true, individualToggle: true, onEnabled: () => this.startDraw(), onDisabled: () => this.endDraw()},
{name: 'measure', icon: 'straighten', toggle: true, individualToggle: true, click: () => this.measure()},
{name: 'delete', icon: 'delete', toggle: true, individualToggle: true},
{name: 'style', icon: 'terrain', enabled: true, toggle: true, onEnabled: () => this.style = 'terrain', onDisabled: () => this.style = 'satellite'},
{name: 'compass', icon: 'explore', click: () => this.calibrate()}
], [
{name: 'messages', icon: 'chat', hidden: true},
{name: 'identity', icon: 'perm_identity', hidden: true},
{name: 'settings', icon: 'settings', hidden: true}
]];
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;
if(this.position.heading != null) {
let marker: HTMLElement = document.querySelector('img[src*="arrow.png"]');
if(marker) marker.style.transform = `rotate(${this.position.heading}deg)`
}
}
});
physicsService.requireCalibration.subscribe(() => {
snackBar.open('Compass requires calibration', 'calibrate', {
duration: 5000,
panelClass: 'bg-warning,text-white'
}).onAction().subscribe(() => this.calibrate());
});
}
addMarker() {
this.mapClick.pipe(skip(1), take(1)).subscribe(coords => {
this.menu[1][0].enabled = false;
let marker = new google.maps.Marker({
map: this.mapApi,
position: {lat: coords.lat, lng: coords.lng}
});
google.maps.event.addListener(marker, 'click', () => {
if(this.menu[1][3].enabled) marker.setMap(null)
});
});
}
measure() {
let deg2rad = (deg) => deg * (Math.PI/180);
let distanceInM = (lat1, lon1, lat2, lon2) => {
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
};
let first;
this.mapClick.pipe(skip(1), take(2)).subscribe(coords => {
if(!first) {
first = coords;
} else {
this.menu[1][2].enabled = false;
let line = new google.maps.Polyline({
map: this.mapApi,
path: [first, coords],
strokeColor: '#f00',
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 marker = new google.maps.Marker({
map: this.mapApi,
icon: null,
position: {lat: (first.lat + coords.lat) / 2, lng: (first.lng + coords.lng) / 2},
label: distance >= 1000 ? `${Math.round(distance / 100) / 10} km` : `${Math.round(distance)} m`
});
google.maps.event.addListener(line, 'click', () => {
if(this.menu[1][3].enabled) {
line.setMap(null);
marker.setMap(null);
}
})
google.maps.event.addListener(marker, 'click', () => {
if(this.menu[1][3].enabled) {
line.setMap(null);
marker.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.mapApi.setOptions({draggable: false});
let drawHander = () => {
let poly = new google.maps.Polyline({map: this.mapApi, clickable: true});
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.mapApi.setOptions({draggable: true});
this.drawListener.forEach(listener => google.maps.event.removeListener(listener));
this.drawListener = [];
}
}

View File

@ -0,0 +1,24 @@
import {Injectable} from "@angular/core";
import {AngularFirestore, AngularFirestoreCollection, DocumentSnapshot} from "@angular/fire/firestore";
import {map} from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class SyncService {
private collection: AngularFirestoreCollection;
constructor(private db: AngularFirestore) {
this.collection = this.db.collection('Maps');
}
async exists(mapCode: string) {
return (await this.collection.doc(mapCode).ref.get()).exists;
}
load(mapCode: string) {
return this.collection.doc(mapCode).snapshotChanges().pipe(map((snap: any) => {
return Object.assign({}, snap.data, {delete: snap.ref.delete, set: snap.ref.set, update: snap.ref.update});
}))
}
}