decoration.grid.js (6172B)
1 import {PrintStaticLayer} from './decorations'; 2 import L from 'leaflet'; 3 4 function radians(degrees) { 5 return degrees * Math.PI / 180; 6 } 7 8 class Grid extends PrintStaticLayer { 9 minGridIntervalMm = 15; 10 11 fontSizeMm = 3; 12 font = 'verdana'; 13 paddingMm = 1; 14 15 /* eslint-disable array-element-newline*/ 16 intervals = [ 17 1, 1.5, 2, 3.3, 5, 7.5, 18 10, 15, 20, 33, 50, 75, 19 100, 150, 200, 333, 500, 750, 20 1000, 1500, 2000, 4000, 5000, 7500, 21 10000, 15000, 20000, 40000, 50000, 75000, 22 100000, 150000, 200000, 400000, 500000, 750000, 23 1000000, 1500000, 2000000, 4000000, 5000000, 7500000 24 ]; 25 /* eslint-enable array-element-newline*/ 26 27 getGridInterval(printOptions) { 28 const minGridIntervalM = this.minGridIntervalMm / 10 * printOptions.scale; 29 let intervalM; 30 for (intervalM of this.intervals) { 31 if (intervalM > minGridIntervalM) { 32 break; 33 } 34 } 35 return intervalM; 36 } 37 38 formatDistance(x) { 39 let unit; 40 if (x < 1000) { 41 unit = 'm'; 42 } else { 43 x /= 1000; 44 unit = 'km'; 45 } 46 if (x % 1) { 47 x = x.toFixed(1); 48 } 49 return `${x} ${unit}`; 50 } 51 52 _drawGrid(canvas, printOptions) { 53 const metersPerDegree = L.Projection.SphericalMercator.R * Math.PI / 180; 54 const ctx = canvas.getContext('2d'); 55 ctx.beginPath(); 56 const pixelsPerMm = 1 / 25.4 * printOptions.resolution; 57 const intervalM = this.getGridInterval(printOptions); 58 const width = printOptions.destPixelSize.x; 59 const height = printOptions.destPixelSize.y; 60 const mercatorBounds = L.bounds( 61 L.Projection.SphericalMercator.project(printOptions.latLngBounds.getNorthWest()), 62 L.Projection.SphericalMercator.project(printOptions.latLngBounds.getSouthEast()) 63 ); 64 const canvasToMercatorScale = mercatorBounds.getSize().unscaleBy(printOptions.destPixelSize); 65 const rows = []; 66 let y = height; 67 while (true) { 68 let yMerc = mercatorBounds.max.y - y * canvasToMercatorScale.y; 69 let lat = L.Projection.SphericalMercator.unproject(L.point(0, yMerc)).lat; 70 rows.push({lat, y}); 71 if (y < 0) { 72 break; 73 } 74 let lat2 = lat + intervalM / metersPerDegree; 75 let yMerc2 = L.Projection.SphericalMercator.project(L.latLng(lat2, 0)).y; 76 y = (mercatorBounds.max.y - yMerc2) / canvasToMercatorScale.y; 77 } 78 const lineWidthMm = 0.15; 79 let lineWidthPx = lineWidthMm * pixelsPerMm; 80 for (let {color, offset} of [ 81 {color: '#D9D9D9', offset: lineWidthPx / 2}, 82 {color: '#8C8C8C', offset: -lineWidthPx / 2}, 83 ]) { 84 ctx.beginPath(); 85 86 ctx.lineWidth = lineWidthPx; 87 ctx.strokeStyle = color; 88 89 for ({y} of rows) { 90 ctx.moveTo(0, y + offset); 91 ctx.lineTo(width, y + offset); 92 } 93 const pageCanvasCenterX = printOptions.destPixelSize.x / 2; 94 for (let direction of [-1, 1]) { 95 let colN = 0; 96 while (true) { 97 let firstRow = true; 98 let hasPointInPage = false; 99 for (let {lat, y} of rows) { 100 let dx = colN * intervalM / Math.cos(radians(lat)) / canvasToMercatorScale.x; 101 // eslint-disable-next-line max-depth 102 if (dx < pageCanvasCenterX) { 103 hasPointInPage = true; 104 } 105 // eslint-disable-next-line max-depth 106 if (firstRow) { 107 ctx.moveTo(pageCanvasCenterX + dx * direction + offset, y); 108 } else { 109 ctx.lineTo(pageCanvasCenterX + dx * direction + offset, y); 110 } 111 firstRow = false; 112 } 113 if (!hasPointInPage) { 114 break; 115 } 116 colN += 1; 117 } 118 } 119 ctx.stroke(); 120 } 121 } 122 123 _drawLabel(canvas, printOptions) { 124 const intervalM = this.getGridInterval(printOptions); 125 const height = printOptions.destPixelSize.y; 126 const ctx = canvas.getContext('2d'); 127 const caption = 'Grid ' + this.formatDistance(intervalM); 128 const fontSize = this.fontSizeMm / 25.4 * printOptions.resolution; 129 const padding = this.paddingMm / 25.4 * printOptions.resolution; 130 ctx.font = `${fontSize}px ${this.font}`; 131 const textWidth = ctx.measureText(caption).width; 132 ctx.textBaseline = 'bottom'; 133 ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'; 134 ctx.fillRect(0, height - fontSize - 2 * padding, textWidth + 2 * padding, fontSize + 2 * padding); 135 ctx.fillStyle = '#000000'; 136 ctx.fillText(caption, padding, height - padding); 137 } 138 139 async getTilesInfo(printOptions) { 140 return { 141 iterateTilePromises: (function*() { 142 yield { 143 tilePromise: Promise.resolve({ 144 draw: (canvas) => this._drawGrid(canvas, printOptions), 145 isOverlay: true, 146 overlaySolid: false 147 } 148 ), 149 abortLoading: () => { 150 // no actions needed 151 } 152 }; 153 yield { 154 tilePromise: Promise.resolve({ 155 draw: (canvas) => this._drawLabel(canvas, printOptions), 156 isOverlay: true, 157 overlaySolid: true 158 } 159 ), 160 abortLoading: () => { 161 // no actions needed 162 } 163 }; 164 }).bind(this), 165 count: 2 166 }; 167 } 168 } 169 170 export {Grid};