Reorganized
This commit is contained in:
		
							
								
								
									
										40
									
								
								src/app/views/home/home.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/app/views/home/home.component.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
<div class="d-flex w-100 h-100 flex-column justify-content-center align-items-center px-4" style="background-color: #1976d2">
 | 
			
		||||
    <div>
 | 
			
		||||
        <img src="assets/images/logo.png" width="100%" height="auto">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="my-5 text-center">
 | 
			
		||||
        <h3 class="text-white">Collaborative map making... {{typedText | async}}</h3>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col">
 | 
			
		||||
                <button mat-flat-button class="w-100 text-white" style="background-color: #dd4b39">
 | 
			
		||||
                    <i class="fab fa-google mr-2"></i> Sign in with Google
 | 
			
		||||
                </button>
 | 
			
		||||
                <button mat-flat-button class="w-100 mt-2 text-white" style="background-color: #00acee">
 | 
			
		||||
                    <i class="fab fa-twitter mr-2"></i> Sign in with Twitter
 | 
			
		||||
                </button>
 | 
			
		||||
                <button mat-flat-button class="w-100 mt-2 text-white" style="background-color: #3b5998">
 | 
			
		||||
                    <i class="fab fa-facebook mr-2"></i>Sign in with Facebook
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col mt-5 mt-md-0">
 | 
			
		||||
                <button mat-flat-button class="w-100 text-white" style="background-color: #dd0330" (click)="new()">
 | 
			
		||||
                    <mat-icon>add</mat-icon> New Map
 | 
			
		||||
                </button>
 | 
			
		||||
                <div class="d-flex mt-2 align-items-baseline">
 | 
			
		||||
                    <div class="flex-grow-1">
 | 
			
		||||
                        <mat-form-field appearance="fill" class="w-100 text-white mat-padding-0">
 | 
			
		||||
                            <input #mapCode matInput placeholder="Map Code">
 | 
			
		||||
                        </mat-form-field>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="ml-2">
 | 
			
		||||
                        <button mat-flat-button class="w-100 text-white" style="background-color: #dd0330" [routerLink]="['/', mapCode.value]">
 | 
			
		||||
                            <mat-icon>explore</mat-icon> Open
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										28
									
								
								src/app/views/home/home.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/app/views/home/home.component.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
import {Component} from "@angular/core";
 | 
			
		||||
import {Observable, timer} from "rxjs";
 | 
			
		||||
import {map, take} from "rxjs/operators";
 | 
			
		||||
import {Router} from "@angular/router";
 | 
			
		||||
import {SyncService} from "../map/sync.service";
 | 
			
		||||
 | 
			
		||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'home',
 | 
			
		||||
    templateUrl: 'home.component.html'
 | 
			
		||||
})
 | 
			
		||||
export class HomeComponent {
 | 
			
		||||
    phrase = 'If you\'re into that';
 | 
			
		||||
    typedText: Observable<string>;
 | 
			
		||||
 | 
			
		||||
    constructor(private syncService: SyncService, private router: Router) {
 | 
			
		||||
        this.typedText = timer(750, 50).pipe(take(this.phrase.length), map((i: number) => this.phrase.substring(0, i + 1)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async new() {
 | 
			
		||||
        let mapCode: string;
 | 
			
		||||
        do {
 | 
			
		||||
            mapCode = Array(16).fill(0).map(() => chars[Math.round(Math.random() * chars.length)]).join('');
 | 
			
		||||
        } while (await this.syncService.exists(mapCode));
 | 
			
		||||
        return this.router.navigate(['/', mapCode]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								src/app/views/map/map.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/app/views/map/map.component.html
									
									
									
									
									
										Normal 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>
 | 
			
		||||
							
								
								
									
										18
									
								
								src/app/views/map/map.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/app/views/map/map.component.scss
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										177
									
								
								src/app/views/map/map.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/app/views/map/map.component.ts
									
									
									
									
									
										Normal 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 = [];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/app/views/map/sync.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/app/views/map/sync.service.ts
									
									
									
									
									
										Normal 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});
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user