angular-leaflet-measure
Version:
A measure component for Angular 2+ and Leaflet.
463 lines (455 loc) • 14.6 kB
JavaScript
import { Component, Input, Output, EventEmitter, NgModule } from '@angular/core';
import { distance, combine, area, lineToPolygon } from '@turf/turf';
import { LayerGroup } from 'leaflet';
import { CommonModule } from '@angular/common';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
const vertexStyle = {
radius: 5,
fillColor: "#74a9cf",
color: "#74a9cf",
weight: 0,
opacity: 1,
fillOpacity: 1,
};
const previewVertexStyle = {
radius: 12,
fillColor: "#bdc9e1",
color: "#bdc9e1",
weight: 1,
opacity: 1,
fillOpacity: 1,
interactive: false
};
const lineStyle = {
color: '#0570b0',
opacity: 1,
};
const previewLineStyle = {
dashArray: '5,5',
opacity: 0.7
};
const polygonStyle = {
color: '#74a9cf',
fillColor: '#74a9cf',
opacity: 0,
weight: 0,
fillOpacity: 0.4
};
const StyleRepository = {
vertexStyle,
previewVertexStyle,
lineStyle,
previewLineStyle,
polygonStyle
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class MeasureComponent {
constructor() {
this.isEnabled = new EventEmitter();
this.distance = 0;
this.area = 0;
this.enabled = false;
this.drawLayer = new LayerGroup();
this.vertexLayer = new LayerGroup();
this.lineLayer = new LayerGroup();
this.polygonLayer = new LayerGroup();
this.previewLineLayer = new LayerGroup();
this.previewVertexLayer = new LayerGroup();
}
/**
* @return {?}
*/
ngOnInit() {
this.drawLayer = new this.L.LayerGroup();
this.vertexLayer = new this.L.LayerGroup();
this.lineLayer = new this.L.LayerGroup();
this.polygonLayer = new this.L.LayerGroup();
this.previewLineLayer = new this.L.LayerGroup();
this.previewVertexLayer = new this.L.LayerGroup();
}
/**
* @return {?}
*/
ngAfterViewInit() {
this.drawLayer.addLayer(this.vertexLayer);
this.drawLayer.addLayer(this.lineLayer);
this.drawLayer.addLayer(this.previewLineLayer);
this.drawLayer.addLayer(this.previewVertexLayer);
this.drawLayer.addLayer(this.polygonLayer);
}
/**
* @return {?}
*/
get distanceLabel() {
const /** @type {?} */ distanceLabel = this.formatDistanceLabel(this.distance);
return distanceLabel;
}
/**
* @return {?}
*/
get areaLabel() {
const /** @type {?} */ areaLabel = this.formatAreaLabel(this.area);
return areaLabel;
}
/**
* @param {?} event
* @return {?}
*/
toggleEnabled(event) {
event.stopPropagation();
this.enabled = !this.enabled;
if (this.enabled) {
this.enableDrawMode();
}
else {
this.disableDrawMode();
}
}
/**
* @param {?} distance
* @return {?}
*/
formatDistanceLabel(distance$$1) {
let /** @type {?} */ distanceLabel = '';
if (distance$$1 < 1000) {
distanceLabel = distance$$1.toFixed(2).toString() + ' m';
}
else {
distanceLabel = (distance$$1 / 1000.0).toFixed(2).toString() + ' km';
}
return distanceLabel;
}
/**
* @param {?} area
* @return {?}
*/
formatAreaLabel(area$$1) {
const /** @type {?} */ hectare = 10000;
const /** @type {?} */ squareKm = 1000000;
let /** @type {?} */ areaLabel = '';
if (area$$1 < hectare) {
areaLabel = area$$1.toFixed(0).toString() + ' m2';
}
else if (area$$1 < squareKm) {
areaLabel = (area$$1 / hectare).toFixed(1).toString() + ' ha';
}
else {
areaLabel = (area$$1 / squareKm).toFixed(2).toString() + ' km2';
}
return areaLabel;
}
/**
* @return {?}
*/
enableDrawMode() {
this.isEnabled.emit(true);
this.map.addLayer(this.drawLayer);
this.addListeners();
}
/**
* @return {?}
*/
disableDrawMode() {
this.isEnabled.emit(false);
this.removeListeners();
this.map.removeLayer(this.drawLayer);
this.vertexLayer.clearLayers();
this.lineLayer.clearLayers();
this.previewLineLayer.clearLayers();
this.previewVertexLayer.clearLayers();
this.polygonLayer.clearLayers();
this.distance = 0;
}
/**
* @return {?}
*/
disable() {
this.enabled = false;
this.disableDrawMode();
}
/**
* @return {?}
*/
showInfoBox() {
return this.enabled;
}
/**
* @param {?} latlng
* @return {?}
*/
addVertex(latlng) {
const /** @type {?} */ vertex = this.createVertexFromLatLng(latlng);
this.vertexLayer.addLayer(this.L.geoJson(vertex, {
pointToLayer: (feature, latlng) => {
return this.L.circleMarker(latlng, StyleRepository.vertexStyle);
}
}));
}
/**
* @param {?} latlng
* @return {?}
*/
createVertexFromLatLng(latlng) {
const /** @type {?} */ vertex = {
type: 'Point',
coordinates: [latlng.lng, latlng.lat]
};
return vertex;
}
/**
* @return {?}
*/
drawLine() {
const /** @type {?} */ vertices = this.vertexLayer.toGeoJSON().features;
const /** @type {?} */ line = this.createLine(vertices);
const /** @type {?} */ lineLayer = this.L.geoJson(line, {
style: StyleRepository.lineStyle
});
this.lineLayer.clearLayers();
this.lineLayer.addLayer(lineLayer);
lineLayer.eachLayer(layer => {
layer.bindTooltip(this.formatDistanceLabel(layer.feature.geometry.distance), {
permanent: true,
className: 'measure-distance-tooltip',
direction: 'center',
}).openTooltip();
});
}
/**
* @param {?} vertices
* @return {?}
*/
createLine(vertices) {
const /** @type {?} */ line = {
type: 'LineString',
coordinates: []
};
const /** @type {?} */ lines = [];
if (vertices.length > 1) {
// This is the part that is actually drawn. It needs to be split up in edges for the labels to work.
for (let /** @type {?} */ i = 0; i < vertices.length - 1; i++) {
const /** @type {?} */ copiedLine = JSON.parse(JSON.stringify(line));
copiedLine.coordinates.push(vertices[i].geometry.coordinates, vertices[i + 1].geometry.coordinates);
copiedLine.distance = this.measureLine(copiedLine.coordinates);
lines.push(copiedLine);
}
// This is the line that is actually measured.
vertices.forEach(vertex => line.coordinates.push(vertex.geometry.coordinates));
this.distance = this.measureLine(vertices);
}
return { type: 'FeatureCollection', features: lines };
}
/**
* @param {?} vertices
* @return {?}
*/
measureLine(vertices) {
let /** @type {?} */ distance$$1 = 0;
for (let /** @type {?} */ i = 0; i < vertices.length - 1; i++) {
distance$$1 += distance(vertices[i], vertices[i + 1], { units: 'meters' });
}
return distance$$1;
}
/**
* @param {?} toLatLng
* @return {?}
*/
drawPreviewLine(toLatLng) {
const /** @type {?} */ previewLine = {
type: 'LineString',
coordinates: [[toLatLng.lng, toLatLng.lat]]
};
const /** @type {?} */ vertices = this.vertexLayer.toGeoJSON().features;
if (vertices.length >= 1) {
const /** @type {?} */ lastVertex = vertices[vertices.length - 1];
previewLine.coordinates.unshift(lastVertex.geometry.coordinates);
}
this.previewLineLayer.clearLayers();
this.previewLineLayer.addLayer(this.L.geoJson(previewLine, {
style: StyleRepository.previewLineStyle
}));
}
/**
* @param {?} latlng
* @return {?}
*/
drawPreviewVertex(latlng) {
const /** @type {?} */ vertex = this.createVertexFromLatLng(latlng);
this.previewVertexLayer.clearLayers();
this.previewVertexLayer.addLayer(this.L.geoJson(vertex, {
pointToLayer: (feature, latlng) => {
return this.L.circleMarker(latlng, StyleRepository.previewVertexStyle);
}
}));
}
/**
* @return {?}
*/
drawPolygon() {
this.polygonLayer.clearLayers();
let /** @type {?} */ area$$1 = 0;
const /** @type {?} */ vertices = this.vertexLayer.toGeoJSON().features;
const /** @type {?} */ lineLayer = this.L.geoJson(this.createLine(vertices));
if (vertices.length > 3) {
const /** @type {?} */ isPolygon = this.isItAPolygon(lineLayer.toGeoJSON());
if (isPolygon) {
const /** @type {?} */ polygon = this.createPolygonFromLineFeatureCollection(lineLayer.toGeoJSON());
area$$1 = this.calculatePolygonArea(polygon);
const /** @type {?} */ polygonLayer = this.L.geoJson(polygon, { style: StyleRepository.polygonStyle });
this.polygonLayer.addLayer(polygonLayer);
polygonLayer.bindTooltip(this.formatAreaLabel(area$$1), {
permanent: true,
className: 'measure-area-tooltip',
direction: 'center',
}).openTooltip();
}
}
this.area = area$$1;
}
/**
* @param {?} line
* @return {?}
*/
isItAPolygon(line) {
let /** @type {?} */ isPolygon = false;
const /** @type {?} */ mergedLineFeatureCollection = /** @type {?} */ (combine(line));
const /** @type {?} */ mergedLine = mergedLineFeatureCollection.features[0];
const /** @type {?} */ startVertex = mergedLine.geometry.coordinates[0][0];
const /** @type {?} */ endVertex = mergedLine.geometry.coordinates[mergedLine.geometry.coordinates.length - 1][1];
const /** @type {?} */ startEndDistance = distance(startVertex, endVertex, { units: 'meters' });
const /** @type {?} */ proximityThreshold = this.distance * 0.025;
if (startEndDistance <= proximityThreshold) {
isPolygon = true;
}
return isPolygon;
}
/**
* @param {?} polygon
* @return {?}
*/
calculatePolygonArea(polygon) {
const /** @type {?} */ area$$1 = area(polygon);
return area$$1;
}
/**
* @param {?} lineFeatureCollection
* @return {?}
*/
createPolygonFromLineFeatureCollection(lineFeatureCollection) {
const /** @type {?} */ mergedLineFeatureCollection = /** @type {?} */ (combine(lineFeatureCollection));
const /** @type {?} */ mergedLine = mergedLineFeatureCollection.features[0];
const /** @type {?} */ lineString = /** @type {?} */ ({
coordinates: [],
type: 'LineString'
});
// Dissolve multilinestring to linestring
mergedLine.geometry.coordinates.forEach(line => lineString.coordinates.push(line[0], line[1]));
const /** @type {?} */ polygon = lineToPolygon(lineString);
return polygon;
}
/**
* @param {?} event
* @return {?}
*/
onMouseMove(event) {
this.drawPreviewVertex(event.latlng);
// this.drawPreviewLine(event.latlng);
}
/**
* @param {?} event
* @return {?}
*/
onClick(event) {
this.addVertex(event.latlng);
this.drawLine();
this.drawPolygon();
}
/**
* @return {?}
*/
addListeners() {
this.map.on('click', this.onClick, this);
this.map.on('mousemove', this.onMouseMove, this);
}
/**
* @return {?}
*/
removeListeners() {
this.map.off('click', this.onClick, this);
this.map.off('mousemove', this.onMouseMove, this);
}
}
MeasureComponent.decorators = [
{ type: Component, args: [{
selector: 'app-measure',
template: `<div class="measure-container">
<button (click)="toggleEnabled($event)" type="button" name="button">
Afstand meten
</button>
<div *ngIf="showInfoBox()" class="measure-infobox">
<span (click)="disable()" class="close-button">
x
</span>
<h4 class="measure-infobox-title">
Afstand meten
</h4>
<p>
Klik op de kaart om te meten.
</p>
<p *ngIf="distance > 0">
Totale lengte: {{distanceLabel}}
</p>
<p *ngIf="area > 0">
Oppervlakte: {{areaLabel}}
</p>
</div>
</div>
`,
styles: [`.measure-container{z-index:401;position:absolute;top:10px;right:10px}h4,p{margin:0 0 5px}h4{font-size:15px}button{border:2px solid rgba(0,0,0,.2);background:#fff;padding:10px;border-radius:2px;position:relative;font-weight:700}button:hover{background:#eee;cursor:pointer}.measure-infobox{background:#fff;width:160px;margin:0 auto;z-index:500;position:absolute;top:0;right:0;padding:10px 10px 3px;border:2px solid rgba(0,0,0,.2)}.close-button{position:absolute;top:0;right:7px;font-weight:700;font-size:15px;color:#ccc}.close-button:hover{color:#111;cursor:pointer}.measure-distance-tooltip{border-radius:0!important;padding-top:2px;padding-bottom:2px}.measure-area-tooltip{font-weight:700}`]
},] },
];
/** @nocollapse */
MeasureComponent.ctorParameters = () => [];
MeasureComponent.propDecorators = {
"map": [{ type: Input },],
"L": [{ type: Input },],
"isEnabled": [{ type: Output },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class MeasureModule {
}
MeasureModule.decorators = [
{ type: NgModule, args: [{
imports: [
CommonModule
],
declarations: [MeasureComponent],
exports: [
MeasureComponent
]
},] },
];
/** @nocollapse */
MeasureModule.ctorParameters = () => [];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Generated bundle index. Do not edit.
*/
export { MeasureModule, MeasureComponent as ɵa };
//# sourceMappingURL=angular-leaflet-measure.js.map