/**
* @module webgis4u/ol/control/Measure
*/
import Control from 'ol/control/Control';
import LineString from 'ol/geom/LineString';
import Polygon from 'ol/geom/Polygon';
import VectorLayer from 'ol/layer/Vector';
import Draw from 'ol/interaction/Draw';
import VectorSource from 'ol/source/Vector';
import { createElement } from '../../util/dom/createElement';
import { formatUnitValue } from '../../util/string';
import { reproject } from '../proj/reproject';
import { MODIFY_FILL, MODIFY_STROKE, SELECT_STROKE, SELECT_FILL } from '../style/color';
import { getStyle } from '../style/style';
import { CSS_CONTROL_PREFIX } from './common';
import './Measure/Measure.scss';
import { EPSG31287_ID } from '../proj/austria';
/**
* Enum describing the measurement type.
* @readonly
* @enum {string}
*/
export const MeasurementTypeEnum = {
/** Area */
Area: 'area',
/** Distance */
Distance: 'distance',
};
const DFEAULT_MEASUREMENT_TYPE = MeasurementTypeEnum.Distance;
/**
* The CSS class for the contorl
*/
export const CSS_CLASS = `${CSS_CONTROL_PREFIX}-measure-output`;
/**
* @param {MeasurementTypeEnum} measurementType The interaction type
* @returns {ol.geom.GeometryType}
*/
function GetClassForType(measurementType) {
switch (measurementType) {
case MeasurementTypeEnum.Area: return 'Polygon';
case MeasurementTypeEnum.Distance: return 'LineString';
default: throw new Error('Unsupported type');
}
}
/**
* @param {MeasurementTypeEnum} value The value
* @returns {MeasurementTypeEnum} The passed value or the default type
*/
function getMeasurementTypeOrDefault(value) {
const keys = Object.keys(MeasurementTypeEnum);
const result = keys.find(key => MeasurementTypeEnum[key] === value);
return (result)
? value
: DFEAULT_MEASUREMENT_TYPE;
}
/**
* Control for measuring
*/
class Measure extends Control {
/**
* The limit above the distance is displayed in km (default < 1000). You can
* override it with your individual value if necessary.
* @type {integer}
*/
static limit4m = 1000;
/**
* The limit when the area is displayed in km2 (default < 1000000). You can
* override it with your individual value if necessary.
* @type {integer}
*/
static limit4m2 = 1000000;
/**
* The type
* @type {string}
*/
measurementType_ = DFEAULT_MEASUREMENT_TYPE;
/**
* The old map
* @type {ol.Map|null}
*/
oldMap_ = null;
constructor(options) {
const element = createElement({
tag: 'div',
cssClass: CSS_CLASS,
});
super({
element,
...options,
});
const source = new VectorSource();
this.vector = new VectorLayer({
source,
style: getStyle(3, 6, SELECT_STROKE, SELECT_FILL, this.lineDash),
});
const initialMeasurementType = options && options.type;
this.measurementType_ = getMeasurementTypeOrDefault(initialMeasurementType);
}
/**
* @returns {MeasurementTypeEnum} the current type
*/
get measurementType() { return this.measurementType_; }
/**
* Sets a new type
* @param {MeasurementTypeEnum} value The type
*/
set measurementType(value) {
this.measurementType_ = getMeasurementTypeOrDefault(value);
this.addInteraction(this.measurementType_);
}
/**
* @returns {MeasurementTypeEnum} the current type
*/
getType() { return this.measurementType; }
/**
* Sets a new type
* @param {MeasurementTypeEnum} value The type
*/
setType(value) { this.measurementType = value; }
/**
* @inheritdoc
*/
setMap(map) {
// Clean up if an old map exists
if (this.oldMap_) {
/* remove all measure interactions (if any) */
this.vector.getSource().clear();
this.oldMap_.removeLayer(this.vector);
this.oldMap_.removeInteraction(this.draw);
this.oldMap_.un('pointermove', this.handleMeasurementShouldUpdate);
this.toggleElement(false);
}
this.oldMap_ = map;
if (map) {
this.measurementType = this.measurementType;
this.oldMap_.addLayer(this.vector);
this.oldMap_.on('pointermove', this.handleMeasurementShouldUpdate);
}
super.setMap(map);
}
/**
* Toggles the elements visibility
* @param {boolean} isVisible If `true`, the element will be displayed
*
* @private
*/
toggleElement(isVisible) {
const { element } = this;
if (!element) { return; }
element.setAttribute('data-visible', isVisible);
}
handleMeasurementShouldUpdate = () => {
this.updateMeasurement();
}
/**
* Updates the measurement
*
* @private
*/
updateMeasurement() {
if (!this.sketch) { return; }
const geom = this.sketch.getGeometry();
if (!geom) { return; }
const sourceProjection = this.oldMap_.getView().getProjection().getCode();
const reprojected = reproject(geom, sourceProjection, EPSG31287_ID);
let text = '';
if (geom instanceof Polygon) {
text = this.displayArea(reprojected.getArea());
} else if (geom instanceof LineString) {
text = this.displayLength(reprojected.getLength());
}
this.element.innerHTML = text;
}
/**
* Add the interaction
* @param {MeasurementTypeEnum} measurementType
*
* @private
*/
addInteraction(measurementType) {
const typeConstructor = GetClassForType(measurementType);
this.draw = new Draw({
source: this.source,
type: typeConstructor,
style: getStyle(3, 6, MODIFY_STROKE, MODIFY_FILL, this.lineDash),
});
this.oldMap_.addInteraction(this.draw);
this.draw.on('drawstart', this.onDrawStart);
this.draw.on('drawend', this.onDrawEnd);
}
/**
* Event handler for the draw start event
* @private
*/
onDrawStart = (evt) => {
// set sketch
this.sketch = evt.feature;
this.toggleElement(true);
}
/**
* Event handler for the draw end event
* @private
*/
onDrawEnd = () => {
// unset sketch
this.sketch = null;
}
/**
* Formats the length output.
* @param {ol.geom.LineString} length The length in m
* @return {string} The formated length.
*
* @private
*/
displayLength(length) {
const formatedUnitValue = formatUnitValue({
value: length,
decimals: 2,
units: [
{ abbreviation: 'm' },
{ abbreviation: 'km', factor: 1000, limit: Measure.limit4m },
],
});
return `Länge: ${formatedUnitValue}`;
}
/**
* Formats the area output.
* @param {number} area The area.
* @returns {string} The formated area.
*
* @private
*/
displayArea(area) {
const formatedUnitValue = formatUnitValue({
value: area,
decimals: 2,
units: [
{ abbreviation: 'm<sup>2</sup>' },
{ abbreviation: 'km<sup>2</sup>', factor: 1000000, limit: Measure.limit4m2 },
],
});
return `Fläche: ${formatedUnitValue}`;
}
}
export default Measure;