tethys.backend/resources/js/Components/Map/map.component.vue
Arno Kaimbacher 005df2e454 - added backup codes for 2 factor authentication
- npm updates
- coverage validation: elevation ust be positive, depth must be negative
- vinejs-provider.js: get enabled extensions from database, not via validOptions.extnames
- vue components for backup codes: e.g.: PersonalSettings.vue
- validate spaital coverage in leaflet map: draw.component.vue, map.component.vue
- add backup code authentication into Login.vue
- preset to use no preferred reviewer: Release.vue
- 2 new vinejs validation rules: file_scan.ts and file-length.ts
2024-07-08 13:52:20 +02:00

379 lines
14 KiB
Vue

<template>
<div style="position: relative">
<!-- <Map className="h-36" :center="state.center" :zoom="state.zoom"> // map component content </Map> -->
<div :id="mapId" class="rounded">
<div class="dark:bg-slate-900 bg-slate flex flex-col">
<ZoomControlComponent ref="zoom" :mapId="mapId" />
<DrawControlComponent ref="draw" :mapId="mapId" :southWest="southWest" :northEast="northEast" />
</div>
</div>
<div class="gba-control-validate btn-group-vertical">
<button
class="min-w-27 inline-flex cursor-pointer justify-center items-center whitespace-nowrap focus:outline-none transition-colors duration-150 border rounded ring-blue-700 text-black text-sm p-1"
type="button"
@click.stop.prevent="validateBoundingBox"
:class="[validBoundingBox ? 'cursor-not-allowed bg-green-500 is-active' : 'bg-red-500 ']"
>
<!-- <BaseIcon v-if="mdiMapCheckOutline" :path="mdiMapCheckOutline" /> -->
{{ label }}
</button>
</div>
</div>
</template>
<script lang="ts">
import { EventEmitter } from './EventEmitter';
import { Component, Vue, Prop, Ref } from 'vue-facing-decorator';
import { Map } from 'leaflet/src/map/index';
import { Control } from 'leaflet/src/control/Control';
import { LatLngBoundsExpression, LatLngBounds } from 'leaflet/src/geo/LatLngBounds';
// import { toLatLng } from 'leaflet/src/geo/LatLng';
import { LatLng } from 'leaflet'; //'leaflet/src/geo/LatLng';
import { tileLayerWMS } from 'leaflet/src/layer/tile/TileLayer.WMS';
import { Attribution } from 'leaflet/src/control/Control.Attribution';
import { mdiMapCheckOutline } from '@mdi/js';
import BaseIcon from '@/Components/BaseIcon.vue';
import { MapOptions } from './MapOptions';
import { LayerOptions, LayerMap } from './LayerOptions';
import { MapService } from '@/Stores/map.service';
import ZoomControlComponent from './zoom.component.vue';
import DrawControlComponent from './draw.component.vue';
import { Coverage } from '@/Dataset';
import { canvas } from 'leaflet/src/layer/vector/Canvas';
import { svg } from 'leaflet/src/layer/vector/SVG';
import Notification from '@/utils/toast';
Map.include({
// @namespace Map; @method getRenderer(layer: Path): Renderer
// Returns the instance of `Renderer` that should be used to render the given
// `Path`. It will ensure that the `renderer` options of the map and paths
// are respected, and that the renderers do exist on the map.
getRenderer: function (layer) {
// @namespace Path; @option renderer: Renderer
// Use this specific instance of `Renderer` for this path. Takes
// precedence over the map's [default renderer](#map-renderer).
var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
if (!renderer) {
renderer = this._renderer = this._createRenderer();
}
if (!this.hasLayer(renderer)) {
this.addLayer(renderer);
}
return renderer;
},
_getPaneRenderer: function (name) {
if (name === 'overlayPane' || name === undefined) {
return false;
}
var renderer = this._paneRenderers[name];
if (renderer === undefined) {
renderer = this._createRenderer({ pane: name });
this._paneRenderers[name] = renderer;
}
return renderer;
},
_createRenderer: function (options) {
// @namespace Map; @option preferCanvas: Boolean = false
// Whether `Path`s should be rendered on a `Canvas` renderer.
// By default, all `Path`s are rendered in a `SVG` renderer.
return (this.options.preferCanvas && canvas(options)) || svg(options);
},
});
const DEFAULT_BASE_LAYER_NAME = 'BaseLayer';
// const DEFAULT_BASE_LAYER_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
const DEFAULT_BASE_LAYER_ATTRIBUTION = '&copy; <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors';
@Component({
name: 'MapComponent',
components: {
ZoomControlComponent,
DrawControlComponent,
BaseIcon,
},
})
export default class MapComponent extends Vue {
/**
* A map with the given ID is created inside this component.
* This ID can be used the get the map instance over the map cache service.
*/
@Prop()
public mapId: string;
/**
* The corresponding leaflet map options (see: https://leafletjs.com/reference-1.3.4.html#map-option)
*/
@Prop()
public mapOptions: MapOptions;
@Prop()
public coverage: Coverage;
// markerService: MarkerService
/**
* Bounds for the map
*/
@Prop({ default: null })
public fitBounds: LatLngBoundsExpression;
/**
* Describes the the zoom control options (see: https://leafletjs.com/reference-1.3.4.html#control-zoom)
*/
@Prop()
public zoomControlOptions: Control.ZoomOptions;
@Prop()
public baseMaps: LayerMap;
get label(): string {
return this.validBoundingBox ? ' valid' : 'invalid';
}
get validBoundingBox(): boolean {
let isValidNumber =
(typeof this.coverage.x_min === 'number' || !isNaN(Number(this.coverage.x_min))) &&
(typeof this.coverage.y_min === 'number' || !isNaN(Number(this.coverage.y_min))) &&
(typeof this.coverage.x_max === 'number' || !isNaN(Number(this.coverage.x_max))) &&
(typeof this.coverage.y_max === 'number' || !isNaN(Number(this.coverage.y_max)));
let isBoundValid = true;
if (isValidNumber) {
let _southWest: LatLng = new LatLng(this.coverage.y_min, this.coverage.x_min);
let _northEast: LatLng = new LatLng(this.coverage.y_max, this.coverage.x_max);
const bounds = new LatLngBounds(this.southWest, this.northEast);
if (!bounds.isValid() || !(_southWest.lat < _northEast.lat && _southWest.lng < _northEast.lng)) {
// this.draw.removeShape();
// Notification.showTemporary('Bounds are not valid.');
isBoundValid = false;
}
}
return isValidNumber && isBoundValid;
}
@Ref('zoom') private zoom: ZoomControlComponent;
@Ref('draw') private draw: DrawControlComponent;
// services:
mapService = MapService();
mdiMapCheckOutline = mdiMapCheckOutline;
southWest: LatLng;
northEast: LatLng;
/**
* Informs when initialization is done with map id.
*/
public onMapInitializedEvent: EventEmitter<string> = new EventEmitter<string>();
public map!: Map;
// protected drawnItems!: FeatureGroup<any>;
validateBoundingBox() {
if (this.validBoundingBox == false) {
this.draw.removeShape();
Notification.showError('Bounds are not valid.');
return;
}
this.map.control && this.map.control.disable();
var _this = this;
// // _this.locationErrors.length = 0;
// this.drawnItems.clearLayers();
// //var xmin = document.getElementById("xmin").value;
// var xmin = (<HTMLInputElement>document.getElementById("xmin")).value;
// // var ymin = document.getElementById("ymin").value;
// var ymin = (<HTMLInputElement>document.getElementById("ymin")).value;
// //var xmax = document.getElementById("xmax").value;
// var xmax = (<HTMLInputElement>document.getElementById("xmax")).value;
// //var ymax = document.getElementById("ymax").value;
// var ymax = (<HTMLInputElement>document.getElementById("ymax")).value;
// var bounds = [[ymin, xmin], [ymax, xmax]];
// let _southWest: LatLng;
// let _northEast: LatLng;
// if (this.coverage.x_min && this.coverage.y_min) {
let _southWest: LatLng = new LatLng(this.coverage.y_min, this.coverage.x_min);
// }
// if (this.coverage.x_max && this.coverage.y_max) {
let _northEast: LatLng = new LatLng(this.coverage.y_max, this.coverage.x_max);
// }
const bounds = new LatLngBounds(this.southWest, this.northEast);
if (!bounds.isValid() || !(_southWest.lat < _northEast.lat && _southWest.lng < _northEast.lng)) {
this.draw.removeShape();
Notification.showTemporary('Bounds are not valid.');
} else {
// this.draw.drawShape(_southWest, _northEast);
try {
this.draw.drawShape(_southWest, _northEast);
_this.map.fitBounds(bounds);
// var boundingBox = L.rectangle(bounds, { color: "#005F6A", weight: 1 });
// // this.geolocation.xmin = xmin;
// // this.geolocation.ymin = ymin;
// // this.geolocation.xmax = xmax;
// // this.geolocation.ymax = ymax;
// _this.drawnItems.addLayer(boundingBox);
// _this.map.fitBounds(bounds);
// this.options.message = "valid bounding box";
// this.$toast.success("valid bounding box", this.options);
Notification.showSuccess('valid bounding box');
} catch (err) {
// this.options.message = e.message;
// // _this.errors.push(e);
// this.$toast.error(e.message, this.options);
Notification.showTemporary('An error occurred while drawing bounding box');
// generatingCodes.value = false;
throw err;
}
}
}
mounted(): void {
this.initMap();
}
unmounted() {
this.map.off('zoomend zoomlevelschange');
}
// @Emit(this.onMapInitializedEvent)
protected initMap(): void {
// let map: Map = (this.map = this.mapService.getMap(this.mapId));
let map: Map = (this.map = new Map(this.mapId, this.mapOptions));
this.mapService.setMap(this.mapId, map);
map.scrollWheelZoom.disable();
// return this.mapId;
// this.$emit("onMapInitializedEvent", this.mapId);
this.onMapInitializedEvent.emit(this.mapId);
this.addBaseMap();
const attributionControl = new Attribution().addTo(this.map);
attributionControl.setPrefix(false);
map.on(
'Draw.Event.CREATED',
function (event) {
// drawnItems.clearLayers();
// var type = event.type;
var layer = event.layer;
// if (type === "rectancle") {
// layer.bindPopup("A popup!" + layer.getBounds().toBBoxString());
var bounds = layer.getBounds();
this.coverage.x_min = bounds.getSouthWest().lng;
this.coverage.y_min = bounds.getSouthWest().lat;
// console.log(this.geolocation.xmin);
this.coverage.x_max = bounds.getNorthEast().lng;
this.coverage.y_max = bounds.getNorthEast().lat;
// }
// drawnItems.addLayer(layer);
},
this,
);
// Initialise the FeatureGroup to store editable layers
// let drawnItems = (this.drawnItems = new FeatureGroup());
// map.addLayer(drawnItems);
this.map.on('zoomend zoomlevelschange', this.zoom.updateDisabled, this.zoom);
// if (this.fitBounds) {
// this.map.fitBounds(this.fitBounds);
// }
if (this.coverage.x_min && this.coverage.y_min) {
this.southWest = new LatLng(this.coverage.y_min, this.coverage.x_min);
} else {
this.southWest = new LatLng(46.5, 9.9);
}
if (this.coverage.x_max && this.coverage.y_max) {
this.northEast = new LatLng(this.coverage.y_max, this.coverage.x_max);
} else {
this.northEast = new LatLng(48.9, 16.9);
} // this.northEast = toLatLng(48.9, 16.9);
const bounds = new LatLngBounds(this.southWest, this.northEast);
map.fitBounds(bounds);
if (this.coverage.x_min && this.coverage.x_max && this.coverage.y_min && this.coverage.y_max) {
let _southWest: LatLng;
let _northEast: LatLng;
if (this.coverage.x_min && this.coverage.y_min) {
_southWest = new LatLng(this.coverage.y_min, this.coverage.x_min);
}
if (this.coverage.x_max && this.coverage.y_max) {
_northEast = new LatLng(this.coverage.y_max, this.coverage.x_max);
}
this.draw.drawShape(_southWest, _northEast);
}
}
private addBaseMap(layerOptions?: LayerOptions): void {
if (this.map) {
if (!this.baseMaps || this.baseMaps.size === 0) {
// let bmapgrau = tileLayer('https://{s}.wien.gv.at/basemap/bmapgrau/normal/google3857/{z}/{y}/{x}.png', {
// subdomains: ['maps', 'maps1', 'maps2', 'maps3', 'maps4'],
// attribution: 'Datenquelle: <a href="http://www.basemap.at/">basemap.at</a>',
// });
let osmGgray = tileLayerWMS('https://ows.terrestris.de/osm-gray/service', {
format: 'image/png',
attribution: DEFAULT_BASE_LAYER_ATTRIBUTION,
layers: 'OSM-WMS',
});
layerOptions = {
label: DEFAULT_BASE_LAYER_NAME,
visible: true,
layer: osmGgray,
};
layerOptions.layer.addTo(this.map);
}
}
}
}
</script>
<style scoped lang="css">
/* .leaflet-container {
height: 600px;
width: 100%;
background-color: transparent;
outline-offset: 1px;
} */
.leaflet-container {
height: 600px;
width: 100%;
background: none;
}
.gba-control-validate {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
border-radius: 4px;
position: absolute;
left: 10px;
top: 150px;
z-index: 999;
}
.btn-group-vertical button {
display: block;
margin-left: 0;
margin-top: 0.5em;
}
/* .leaflet-pane {
z-index: 30;
} */
</style>