diff --git a/examples/measure-style.html b/examples/measure-style.html new file mode 100644 index 0000000000..0baea158e7 --- /dev/null +++ b/examples/measure-style.html @@ -0,0 +1,33 @@ +--- +layout: example.html +title: Measure using vector styles +shortdesc: Example of measuring lengths and areas using vector styles. +docs: > +

Using vector styles instead of overlays makes it easy to set up, then modify and + clear a measure. Additional information such as the lengths of individual segments + or sides can be included as required.

+

The getLength() and getArea() + functions calculate spherical lengths and areas for geometries. Lengths are + calculated by assuming great circle segments between geometry coordinates. + Areas are calculated as if edges of polygons were great circle segments.

+

Note that the geometry.getLength() and geometry.getArea() + methods return measures of projected (planar) geometries. These can be very + different than on-the-ground measures in certain situations — in northern + and southern latitudes using Web Mercator for example. For better results, + use the functions in the ol/sphere module.

+tags: "draw, edit, measure, modify, style, vector" +--- +
+
+ + +      + + +      + + +
diff --git a/examples/measure-style.js b/examples/measure-style.js new file mode 100644 index 0000000000..3b7e6dbe34 --- /dev/null +++ b/examples/measure-style.js @@ -0,0 +1,274 @@ +import Map from '../src/ol/Map.js'; +import View from '../src/ol/View.js'; +import { + Circle as CircleStyle, + Fill, + RegularShape, + Stroke, + Style, + Text, +} from '../src/ol/style.js'; +import {Draw, Modify} from '../src/ol/interaction.js'; +import {LineString, Point} from '../src/ol/geom.js'; +import {OSM, Vector as VectorSource} from '../src/ol/source.js'; +import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; +import {getArea, getLength} from '../src/ol/sphere.js'; + +const typeSelect = document.getElementById('type'); +const showSegments = document.getElementById('segments'); +const clearPrevious = document.getElementById('clear'); + +const style = new Style({ + fill: new Fill({ + color: 'rgba(255, 255, 255, 0.2)', + }), + stroke: new Stroke({ + color: 'rgba(0, 0, 0, 0.5)', + lineDash: [10, 10], + width: 2, + }), + image: new CircleStyle({ + radius: 5, + stroke: new Stroke({ + color: 'rgba(0, 0, 0, 0.7)', + }), + fill: new Fill({ + color: 'rgba(255, 255, 255, 0.2)', + }), + }), +}); + +const labelStyle = new Style({ + text: new Text({ + font: '14px Calibri,sans-serif', + fill: new Fill({ + color: 'rgba(255, 255, 255, 1)', + }), + backgroundFill: new Fill({ + color: 'rgba(0, 0, 0, 0.7)', + }), + padding: [3, 3, 3, 3], + textBaseline: 'bottom', + offsetY: -15, + }), + image: new RegularShape({ + radius: 8, + points: 3, + angle: Math.PI, + displacement: [0, 10], + fill: new Fill({ + color: 'rgba(0, 0, 0, 0.7)', + }), + }), +}); + +const tipStyle = new Style({ + text: new Text({ + font: '12px Calibri,sans-serif', + fill: new Fill({ + color: 'rgba(255, 255, 255, 1)', + }), + backgroundFill: new Fill({ + color: 'rgba(0, 0, 0, 0.4)', + }), + padding: [2, 2, 2, 2], + textAlign: 'left', + offsetX: 15, + }), +}); + +const modifyStyle = new Style({ + image: new CircleStyle({ + radius: 5, + stroke: new Stroke({ + color: 'rgba(0, 0, 0, 0.7)', + }), + fill: new Fill({ + color: 'rgba(0, 0, 0, 0.4)', + }), + }), + text: new Text({ + text: 'Drag to modify', + font: '12px Calibri,sans-serif', + fill: new Fill({ + color: 'rgba(255, 255, 255, 1)', + }), + backgroundFill: new Fill({ + color: 'rgba(0, 0, 0, 0.7)', + }), + padding: [2, 2, 2, 2], + textAlign: 'left', + offsetX: 15, + }), +}); + +const segmentStyle = new Style({ + text: new Text({ + font: '12px Calibri,sans-serif', + fill: new Fill({ + color: 'rgba(255, 255, 255, 1)', + }), + backgroundFill: new Fill({ + color: 'rgba(0, 0, 0, 0.4)', + }), + padding: [2, 2, 2, 2], + textBaseline: 'bottom', + offsetY: -12, + }), + image: new RegularShape({ + radius: 6, + points: 3, + angle: Math.PI, + displacement: [0, 8], + fill: new Fill({ + color: 'rgba(0, 0, 0, 0.4)', + }), + }), +}); + +const segmentStyles = [segmentStyle]; + +const formatLength = function (line) { + const length = getLength(line); + let output; + if (length > 100) { + output = Math.round((length / 1000) * 100) / 100 + ' km'; + } else { + output = Math.round(length * 100) / 100 + ' m'; + } + return output; +}; + +const formatArea = function (polygon) { + const area = getArea(polygon); + let output; + if (area > 10000) { + output = Math.round((area / 1000000) * 100) / 100 + ' km\xB2'; + } else { + output = Math.round(area * 100) / 100 + ' m\xB2'; + } + return output; +}; + +const raster = new TileLayer({ + source: new OSM(), +}); + +const source = new VectorSource(); + +const modify = new Modify({source: source, style: modifyStyle}); + +let tipPoint; + +function styleFunction(feature, segments, drawType, tip) { + const styles = [style]; + const geometry = feature.getGeometry(); + const type = geometry.getType(); + let point, label, line; + if (!drawType || drawType === type) { + if (type === 'Polygon') { + point = geometry.getInteriorPoint(); + label = formatArea(geometry); + line = new LineString(geometry.getCoordinates()[0]); + } else if (type === 'LineString') { + point = new Point(geometry.getLastCoordinate()); + label = formatLength(geometry); + line = geometry; + } + } + if (segments && line) { + let count = 0; + line.forEachSegment(function (a, b) { + const segment = new LineString([a, b]); + const label = formatLength(segment); + if (segmentStyles.length - 1 < count) { + segmentStyles.push(segmentStyle.clone()); + } + const segmentPoint = new Point(segment.getCoordinateAt(0.5)); + segmentStyles[count].setGeometry(segmentPoint); + segmentStyles[count].getText().setText(label); + styles.push(segmentStyles[count]); + count++; + }); + } + if (label) { + labelStyle.setGeometry(point); + labelStyle.getText().setText(label); + styles.push(labelStyle); + } + if ( + tip && + type === 'Point' && + !modify.getOverlay().getSource().getFeatures().length + ) { + tipPoint = geometry; + tipStyle.getText().setText(tip); + styles.push(tipStyle); + } + return styles; +} + +const vector = new VectorLayer({ + source: source, + style: function (feature) { + return styleFunction(feature, showSegments.checked); + }, +}); + +const map = new Map({ + layers: [raster, vector], + target: 'map', + view: new View({ + center: [-11000000, 4600000], + zoom: 15, + }), +}); + +map.addInteraction(modify); + +let draw; // global so we can remove it later + +function addInteraction() { + const drawType = typeSelect.value; + const activeTip = + 'Click to continue drawing the ' + + (drawType === 'Polygon' ? 'polygon' : 'line'); + const idleTip = 'Click to start measuring'; + let tip = idleTip; + draw = new Draw({ + source: source, + type: drawType, + style: function (feature) { + return styleFunction(feature, showSegments.checked, drawType, tip); + }, + }); + draw.on('drawstart', function () { + if (clearPrevious.checked) { + source.clear(); + } + modify.setActive(false); + tip = activeTip; + }); + draw.on('drawend', function () { + modifyStyle.setGeometry(tipPoint); + modify.setActive(true); + map.once('pointermove', function () { + modifyStyle.setGeometry(); + }); + tip = idleTip; + }); + modify.setActive(true); + map.addInteraction(draw); +} + +typeSelect.onchange = function () { + map.removeInteraction(draw); + addInteraction(); +}; + +addInteraction(); + +showSegments.onchange = function () { + vector.changed(); + draw.getOverlay().changed(); +};