ol-ext
Version:
A set of cool extensions for OpenLayers (ol) in node modules structure
317 lines (288 loc) • 11.9 kB
JavaScript
/* 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<number>} 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