/* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ import ol_proj_Projection from 'ol/proj/Projection.js' import ol_style_Style from 'ol/style/Style.js' import ol_style_Stroke from 'ol/style/Stroke.js' import ol_style_Fill from 'ol/style/Fill.js' import ol_style_Text from 'ol/style/Text.js' import {transform as ol_proj_transform} from 'ol/proj.js' import {get as ol_proj_get} from 'ol/proj.js' import ol_control_CanvasBase from './CanvasBase.js' /** Draw a graticule on the map. * @constructor * @author mike-000 https://github.com/mike-000 * @author Jean-Marc Viglino https://github.com/viglino * @extends {ol_control_CanvasBase} * @param {Object=} _ol_control_ options. * @param {ol.projectionLike} options.projection projection to use for the graticule, default EPSG:4326 * @param {number} options.maxResolution max resolution to display the graticule * @param {ol_style_Style} options.style Style to use for drawing the graticule, default black / white. Line style is used for drawing lines (no line if not defined). Fill style is used to draw the border. Text style is used to draw coords. * @param {number} options.step step between lines (in proj units), default 1 * @param {number} options.stepCoord show a coord every stepCoord, default 1 * @param {number} options.spacing spacing between lines (in px), default 40px * @param {Array} options.intervals array (in desending order) of intervals (in proj units) constraining which lines will be displayed, default is no contraint (any multiple of step can be used) * @param {number} options.precision precision interval (in proj units) of displayed lines, if the line interval exceeds this more calculations will be used to display curved lines more accurately * @param {number} options.borderWidth width of the border (in px), default 5px * @param {number} options.margin margin of the border (in px), default 0px * @param {number} options.formatCoord a function that takes a coordinate and a position and return the formated coordinate */ var ol_control_Graticule = class olcontrolGraticule extends ol_control_CanvasBase { constructor(options) { options = options || {} // Initialize parent var elt = document.createElement("div") elt.className = "ol-graticule ol-unselectable ol-hidden" super({ element: elt }) this.set('projection', options.projection || 'EPSG:4326') // Use to limit calculation var p = new ol_proj_Projection({ code: this.get('projection') }) var m = p.getMetersPerUnit() this.fac = 1 while (m / this.fac > 10) { this.fac *= 10 } this.fac = 10000 / this.fac this.set('maxResolution', options.maxResolution || Infinity) this.set('step', options.step || 0.1) this.set('stepCoord', options.stepCoord || 1) this.set('spacing', options.spacing || 40) this.set('intervals', options.intervals) this.set('precision', options.precision) this.set('margin', options.margin || 0) this.set('borderWidth', options.borderWidth || 5) this.set('stroke', options.stroke !== false) this.formatCoord = options.formatCoord || function (c) { return c } if (options.style instanceof ol_style_Style) { this.setStyle(options.style) } else { this.setStyle(new ol_style_Style({ stroke: new ol_style_Stroke({ color: "#000", width: 1 }), fill: new ol_style_Fill({ color: "#fff" }), text: new ol_style_Text({ stroke: new ol_style_Stroke({ color: "#fff", width: 2 }), fill: new ol_style_Fill({ color: "#000" }), }) })) } } setStyle(style) { this._style = style } _draw(e) { if (this.get('maxResolution') < e.frameState.viewState.resolution) return var ctx = this.getContext(e) var canvas = ctx.canvas var ratio = e.frameState.pixelRatio var w = canvas.width / ratio var h = canvas.height / ratio var proj = this.get('projection') var map = this.getMap() var bbox = [map.getCoordinateFromPixel([0, 0]), map.getCoordinateFromPixel([w, 0]), map.getCoordinateFromPixel([w, h]), map.getCoordinateFromPixel([0, h]) ] var xmax = -Infinity var xmin = Infinity var ymax = -Infinity var ymin = Infinity for (var i = 0, c; c = bbox[i]; i++) { bbox[i] = ol_proj_transform(c, map.getView().getProjection(), proj) xmax = Math.max(xmax, bbox[i][0]) xmin = Math.min(xmin, bbox[i][0]) ymax = Math.max(ymax, bbox[i][1]) ymin = Math.min(ymin, bbox[i][1]) } var spacing = this.get('spacing') var step = this.get('step') var step2 = this.get('stepCoord') var borderWidth = this.get('borderWidth') var margin = this.get('margin') // Limit max line draw var ds = (xmax - xmin) / step * spacing if (ds > w) { var dt = Math.round((xmax - xmin) / w * spacing / step) step *= dt if (step > this.fac) step = Math.round(step / this.fac) * this.fac } var intervals = this.get('intervals') if (Array.isArray(intervals)) { var interval = intervals[0] for (let i = 0, ii = intervals.length; i < ii; ++i) { if (step >= intervals[i]) { break } interval = intervals[i] } step = interval } var precision = this.get('precision') var calcStep = step if (precision > 0 && step > precision) { calcStep = step / Math.ceil(step / precision) } xmin = (Math.floor(xmin / step)) * step - step ymin = (Math.floor(ymin / step)) * step - step xmax = (Math.floor(xmax / step)) * step + 2 * step ymax = (Math.floor(ymax / step)) * step + 2 * step var extent = ol_proj_get(proj).getExtent() if (extent) { if (xmin < extent[0]) xmin = extent[0] if (ymin < extent[1]) ymin = extent[1] if (xmax > extent[2]) xmax = extent[2] + step if (ymax > extent[3]) ymax = extent[3] + step } var hasLines = this.getStyle().getStroke() && this.get("stroke") var hasText = this.getStyle().getText() var hasBorder = this.getStyle().getFill() ctx.save() ctx.scale(ratio, ratio) ctx.beginPath() ctx.rect(margin, margin, w - 2 * margin, h - 2 * margin) ctx.clip() ctx.beginPath() var txt = { top: [], left: [], bottom: [], right: [] } var x, y, p, p0, p1 for (x = xmin; x < xmax; x += step) { p0 = ol_proj_transform([x, ymin], proj, map.getView().getProjection()) p0 = map.getPixelFromCoordinate(p0) if (hasLines) ctx.moveTo(p0[0], p0[1]) p = p0 for (y = ymin + calcStep; y <= ymax; y += calcStep) { p1 = ol_proj_transform([x, y], proj, map.getView().getProjection()) p1 = map.getPixelFromCoordinate(p1) if (hasLines) ctx.lineTo(p1[0], p1[1]) if (p[1] > 0 && p1[1] < 0) txt.top.push([x, p]) if (p[1] > h && p1[1] < h) txt.bottom.push([x, p]) p = p1 } } for (y = ymin; y < ymax; y += step) { p0 = ol_proj_transform([xmin, y], proj, map.getView().getProjection()) p0 = map.getPixelFromCoordinate(p0) if (hasLines) ctx.moveTo(p0[0], p0[1]) p = p0 for (x = xmin + calcStep; x <= xmax; x += calcStep) { p1 = ol_proj_transform([x, y], proj, map.getView().getProjection()) p1 = map.getPixelFromCoordinate(p1) if (hasLines) ctx.lineTo(p1[0], p1[1]) if (p[0] < 0 && p1[0] > 0) txt.left.push([y, p]) if (p[0] < w && p1[0] > w) txt.right.push([y, p]) p = p1 } } if (hasLines) { ctx.strokeStyle = this.getStyle().getStroke().getColor() ctx.lineWidth = this.getStyle().getStroke().getWidth() ctx.stroke() } // Draw text if (hasText) { ctx.fillStyle = this.getStyle().getText().getFill().getColor() ctx.strokeStyle = this.getStyle().getText().getStroke().getColor() ctx.lineWidth = this.getStyle().getText().getStroke().getWidth() ctx.font = this.getStyle().getText().getFont() ctx.textAlign = 'center' ctx.textBaseline = 'hanging' var t, tf var offset = (hasBorder ? borderWidth : 0) + margin + 2 for (i = 0; t = txt.top[i]; i++) if (!(Math.round(t[0] / this.get('step')) % step2)) { tf = this.formatCoord(t[0], 'top') ctx.strokeText(tf, t[1][0], offset) ctx.fillText(tf, t[1][0], offset) } ctx.textBaseline = 'alphabetic' for (i = 0; t = txt.bottom[i]; i++) if (!(Math.round(t[0] / this.get('step')) % step2)) { tf = this.formatCoord(t[0], 'bottom') ctx.strokeText(tf, t[1][0], h - offset) ctx.fillText(tf, t[1][0], h - offset) } ctx.textBaseline = 'middle' ctx.textAlign = 'left' for (i = 0; t = txt.left[i]; i++) if (!(Math.round(t[0] / this.get('step')) % step2)) { tf = this.formatCoord(t[0], 'left') ctx.strokeText(tf, offset, t[1][1]) ctx.fillText(tf, offset, t[1][1]) } ctx.textAlign = 'right' for (i = 0; t = txt.right[i]; i++) if (!(Math.round(t[0] / this.get('step')) % step2)) { tf = this.formatCoord(t[0], 'right') ctx.strokeText(tf, w - offset, t[1][1]) ctx.fillText(tf, w - offset, t[1][1]) } } // Draw border if (hasBorder) { var fillColor = this.getStyle().getFill().getColor() var color, stroke if (stroke = this.getStyle().getStroke()) { color = this.getStyle().getStroke().getColor() } else { color = fillColor fillColor = "#fff" } ctx.strokeStyle = color ctx.lineWidth = stroke ? stroke.getWidth() : 1 // for (i = 1; i < txt.top.length; i++) { ctx.beginPath() ctx.rect(txt.top[i - 1][1][0], margin, txt.top[i][1][0] - txt.top[i - 1][1][0], borderWidth) ctx.fillStyle = Math.round(txt.top[i][0] / step) % 2 ? color : fillColor ctx.fill() ctx.stroke() } for (i = 1; i < txt.bottom.length; i++) { ctx.beginPath() ctx.rect(txt.bottom[i - 1][1][0], h - borderWidth - margin, txt.bottom[i][1][0] - txt.bottom[i - 1][1][0], borderWidth) ctx.fillStyle = Math.round(txt.bottom[i][0] / step) % 2 ? color : fillColor ctx.fill() ctx.stroke() } for (i = 1; i < txt.left.length; i++) { ctx.beginPath() ctx.rect(margin, txt.left[i - 1][1][1], borderWidth, txt.left[i][1][1] - txt.left[i - 1][1][1]) ctx.fillStyle = Math.round(txt.left[i][0] / step) % 2 ? color : fillColor ctx.fill() ctx.stroke() } for (i = 1; i < txt.right.length; i++) { ctx.beginPath() ctx.rect(w - borderWidth - margin, txt.right[i - 1][1][1], borderWidth, txt.right[i][1][1] - txt.right[i - 1][1][1]) ctx.fillStyle = Math.round(txt.right[i][0] / step) % 2 ? color : fillColor ctx.fill() ctx.stroke() } ctx.beginPath() ctx.fillStyle = color ctx.rect(margin, margin, borderWidth, borderWidth) ctx.rect(margin, h - borderWidth - margin, borderWidth, borderWidth) ctx.rect(w - borderWidth - margin, margin, borderWidth, borderWidth) ctx.rect(w - borderWidth - margin, h - borderWidth - margin, borderWidth, borderWidth) ctx.fill() } ctx.restore() } } export default ol_control_Graticule