UNPKG

angular-leaflet-measure

Version:

A measure component for Angular 2+ and Leaflet.

463 lines (455 loc) 14.6 kB
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