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,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|