gisaf-frontend/src/app/map/gisaf-mapbox/gisaf-ruler.directive.ts

281 lines
6.8 KiB
TypeScript
Raw Normal View History

2024-02-17 12:35:03 +05:30
import { ElementRef, Input, ViewChild, Directive, HostListener } from '@angular/core'
import { Subscription } from 'rxjs'
import { MatButton } from '@angular/material/button'
import { Position } from 'geojson'
import distance from '@turf/distance'
import { Units } from '@turf/helpers'
import { MapService } from '@maplibre/ngx-maplibre-gl'
2024-02-27 11:52:00 +05:30
import { GeoJSONSource, MapMouseEvent } from 'maplibre-gl'
2024-02-17 12:35:03 +05:30
const LAYER_LINE = 'gisaf-ruler-line'
const LAYER_SYMBOL = 'gisaf-ruler-symbol'
const LAYER_DISTANCE = 'gisaf-ruler-dist'
const LAYER_MARKER = 'gisaf-ruler-marker'
const SOURCE_LINE = 'gisaf-ruler-line-source'
const SOURCE_SYMBOL = 'gisaf-ruler-symbol-source'
@Directive({
selector: '[gisafRuler]'
})
export class GisafRulerDirective {
segments: Position[][] = []
startingPoint: Position
labels: string[]
distances: number[]
// XXX: remove typings (maplibre 2.x)
//lineSource: GeoJSONSource
//symbolSource: GeoJSONSource
lineSource: any
symbolSource: any
active: boolean = false
@Input() color: string = '#4264fb'
@Input() units: Units = <Units>'kilometers'
@Input() font: string[] = ['Noto Sans Regular']
@Input() fontSize: number = 14
@Input() markerSize: number = 3
@Input() lineWidth: number = 1
@Input() secondaryColor: string = 'white'
@Input() fontHalo: number = 2
@ViewChild(MatButton) button: MatButton
clickSubscription: Subscription
lineDrawn: boolean
constructor(
public elementRef: ElementRef,
private mapService: MapService
) {}
@HostListener('click')
onClick() {
this.active ? this.stop() : this.start()
}
start() {
this.active = true
this.elementRef.nativeElement.classList.add('-active')
this.segments = []
this.labels = []
this.distances = []
this.lineDrawn = false
this.startingPoint = undefined
this.addLayers()
this.mapService.mapCreated$.subscribe(
() => {
this.clickSubscription = this.mapService.mapEvents.mapClick.subscribe(
evt => this.onMapClick(evt)
)
}
)
}
stop() {
this.active = false
this.elementRef.nativeElement.classList.remove('-active')
this.clickSubscription.unsubscribe()
this.removeLayers()
}
/*
updateLabels() {
let sum = 0
this.labels = this.segments.map((coordinate, index) => {
if (index === 0) return this.labelFormat(0)
sum += distance(this.segments[index - 1], coordinate, {units: this.units})
return this.labelFormat(sum)
})
}
*/
/*
updateDistances() {
this.distances = []
this.segments.map((coordinate, index) => {
if (index === 0) return
this.distances.push(
distance(this.segments[index - 1], coordinate, {units: this.units})
)
})
}
*/
labelFormat(n) {
return n == 0 ? '0' : n < 1 ? `${(n * 1000).toFixed(2)} m`: `${n.toFixed(3)} km`
}
2024-02-27 11:52:00 +05:30
onMapClick(evt: MapMouseEvent) {
2024-02-17 12:35:03 +05:30
const newCoordinate = [evt.lngLat.lng, evt.lngLat.lat]
if (this.lineDrawn) {
if (evt.originalEvent.ctrlKey) {
this.segments.push([this.startingPoint, newCoordinate])
}
else {
this.startingPoint = newCoordinate
this.lineDrawn = false
}
}
else {
if (this.startingPoint) {
this.segments.push([this.startingPoint, newCoordinate])
this.lineDrawn = true
}
else {
this.startingPoint = newCoordinate
}
}
//else {
// if (!this.startingPoint || this.multiLineMode) {
// this.startingPoint = newCoordinate
// }
// else {
// this.segments.push([this.startingPoint, newCoordinate])
// this.startingPoint = undefined
// }
// this.multiLineMode = false
//}
//this.updateLabels()
//this.updateDistances()
this.lineSource.setData(this.getLineStringFeatures())
this.symbolSource.setData(this.getPointFeatureCollection())
}
addLayers() {
const map = this.mapService.mapInstance
map.addSource(SOURCE_LINE, {
type: 'geojson',
data: this.getLineStringFeatures()
}
)
map.addSource(SOURCE_SYMBOL, {
type: 'geojson',
data: this.getPointFeatureCollection()
})
map.addLayer({
id: LAYER_LINE,
type: 'line',
source: SOURCE_LINE,
paint: {
'line-color': this.color,
'line-width': this.lineWidth,
'line-dasharray': [2, 2]
},
})
/*
map.addLayer({
id: LAYER_SYMBOL,
type: 'symbol',
source: SOURCE_SYMBOL,
layout: {
'text-field': '{text}',
'text-font': this.font,
'text-anchor': 'top',
'text-size': this.fontSize,
'text-offset': [0, 0.8],
'text-allow-overlap': true,
},
paint: {
'text-color': this.color,
//'text-halo-color': this.secondaryColor,
//'text-halo-width': this.fontHalo,
},
})
*/
map.addLayer({
id: LAYER_DISTANCE,
type: 'symbol',
source: SOURCE_LINE,
layout: {
'text-field': ['get', 'dist'],
'text-font': this.font,
//'text-anchor': 'top',
'text-size': this.fontSize,
//'text-offset': [0, 0.8],
'text-allow-overlap': true,
'symbol-placement': 'line-center'
},
paint: {
'text-color': this.color,
'text-halo-color': this.secondaryColor,
'text-halo-width': this.fontHalo,
},
})
map.addLayer({
id: LAYER_MARKER,
type: 'circle',
source: SOURCE_SYMBOL,
paint: {
'circle-radius': this.markerSize,
'circle-color': this.color,
},
})
2024-02-27 11:52:00 +05:30
this.lineSource = <GeoJSONSource>map.getSource(SOURCE_LINE)
this.symbolSource = <GeoJSONSource>map.getSource(SOURCE_SYMBOL)
2024-02-17 12:35:03 +05:30
}
removeLayers() {
const map = this.mapService.mapInstance
map.removeLayer(LAYER_LINE)
//map.removeLayer(LAYER_SYMBOL)
map.removeLayer(LAYER_DISTANCE)
map.removeLayer(LAYER_MARKER)
map.removeSource(SOURCE_LINE)
map.removeSource(SOURCE_SYMBOL)
}
getLineStringFeatures(): GeoJSON.FeatureCollection {
return {
type: 'FeatureCollection',
features: this.segments.map(
segment => ({
type: 'Feature',
properties: {
dist: this.labelFormat(distance(segment[0], segment[1]))
},
geometry: {
type: 'LineString',
coordinates: segment,
}
})
)
}
}
getPointFeatureCollection(): GeoJSON.FeatureCollection {
let points: Position[] = []
this.segments.forEach(
s => {
points.push(s[0], s[1])
}
)
if (!!this.startingPoint) {
points.push(this.startingPoint)
}
return {
type: 'FeatureCollection',
features: points.map(
p => ({
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: p,
}
})
)
}
}
}