|
34 | 34 | * See EXPORT_PDF_ZOOM_TABLE_STRICT if z is below extent-fit. |
35 | 35 | */ |
36 | 36 | const EXPORT_PDF_ZOOM_BY_SCALE_DEN = { |
37 | | - 5000: 18, |
38 | | - 10000: 17, |
| 37 | + 5000: 17, |
| 38 | + 10000: 16, |
39 | 39 | 15000: 16, |
40 | 40 | 25000: 15, |
41 | 41 | 50000: 14, |
|
254 | 254 | const scaleEl = document.getElementById("scale-den"); |
255 | 255 | const scaleDen = scaleEl ? Number(scaleEl.value) : 25000; |
256 | 256 | const dpi = EXPORT_RASTER_DPI; |
257 | | - const magEl = document.getElementById("pdf-magnetic-north"); |
258 | | - const magneticNorthUp = !!(magEl && magEl.checked); |
| 257 | + /** PDF magnetic export hidden: georef vs rotation still wrong; re-enable when fixed. */ |
| 258 | + const magneticNorthUp = false; |
259 | 259 | return { paper, orient, scaleDen, dpi, magneticNorthUp }; |
260 | 260 | } |
261 | 261 |
|
|
558 | 558 | } |
559 | 559 |
|
560 | 560 | /** |
561 | | - * Corners of the centre-cropped page in pre-crop canvas pixels → lat/lng via inverse of CSS rotate(-dec) |
562 | | - * on the print map (same φ as boundsCornersRotatedScreen). |
| 561 | + * Map outer html2canvas pixel (origin top-left of rotated outer) → lat/lng (inverse CSS rotate(-dec)). |
563 | 562 | * |
564 | 563 | * @param {L.Map} printMap |
565 | 564 | * @param {number} decDeg east-positive declination |
566 | | - * @param {number} W crop width / inner map px |
567 | | - * @param {number} H crop height |
568 | | - * @param {number} canvasW html2canvas width before centre crop |
569 | | - * @param {number} canvasH |
570 | | - * @param {number} sx crop left (centred) |
571 | | - * @param {number} sy crop top |
572 | | - * @returns {number[]} GPTS length 8 |
| 565 | + * @param {number} W inner map CSS px width |
| 566 | + * @param {number} H inner map CSS px height |
| 567 | + * @param {number} canvasW pre-crop canvas width |
| 568 | + * @param {number} canvasH pre-crop canvas height |
| 569 | + * @param {number} px |
| 570 | + * @param {number} py |
| 571 | + * @param {number} ps pixel scale (html2canvas scale) |
573 | 572 | */ |
574 | | - function gptsFromMagneticCentreCrop(printMap, decDeg, W, H, canvasW, canvasH, sx, sy, pixelScale) { |
575 | | - const ps = pixelScale && pixelScale > 0 ? pixelScale : 1; |
| 573 | + function magneticOuterPxToLatLng(printMap, decDeg, W, H, canvasW, canvasH, px, py, ps) { |
576 | 574 | const Ox = canvasW / 2; |
577 | 575 | const Oy = canvasH / 2; |
578 | 576 | const phi = (-decDeg * Math.PI) / 180; |
579 | 577 | const cos = Math.cos(phi); |
580 | 578 | const sin = Math.sin(phi); |
| 579 | + const rxo = px - Ox; |
| 580 | + const ryo = py - Oy; |
| 581 | + const dx = rxo * cos - ryo * sin; |
| 582 | + const dy = rxo * sin + ryo * cos; |
| 583 | + const lx = W / 2 + dx / ps; |
| 584 | + const ly = H / 2 + dy / ps; |
| 585 | + return printMap.containerPointToLatLng(L.point(lx, ly)); |
| 586 | + } |
581 | 587 |
|
582 | | - function pxToLatLng(px, py) { |
583 | | - const rxo = px - Ox; |
584 | | - const ryo = py - Oy; |
585 | | - const dx = rxo * cos - ryo * sin; |
586 | | - const dy = rxo * sin + ryo * cos; |
587 | | - const lx = W / 2 + dx / ps; |
588 | | - const ly = H / 2 + dy / ps; |
589 | | - return printMap.containerPointToLatLng(L.point(lx, ly)); |
| 588 | + /** |
| 589 | + * @param {L.Map} printMap |
| 590 | + * @param {number} decDeg |
| 591 | + * @param {number} W |
| 592 | + * @param {number} H |
| 593 | + * @param {number} canvasW |
| 594 | + * @param {number} canvasH |
| 595 | + * @param {number} sx |
| 596 | + * @param {number} sy |
| 597 | + * @param {number} sw |
| 598 | + * @param {number} sh |
| 599 | + * @param {number} ps |
| 600 | + * @returns {number[]} |
| 601 | + */ |
| 602 | + function gptsFromMagneticCropRect(printMap, decDeg, W, H, canvasW, canvasH, sx, sy, sw, sh, ps) { |
| 603 | + function p2ll(px, py) { |
| 604 | + return magneticOuterPxToLatLng(printMap, decDeg, W, H, canvasW, canvasH, px, py, ps); |
590 | 605 | } |
591 | | - |
592 | | - const cw = W * ps; |
593 | | - const ch = H * ps; |
594 | | - const swLL = pxToLatLng(sx, sy + ch); |
595 | | - const nwLL = pxToLatLng(sx, sy); |
596 | | - const neLL = pxToLatLng(sx + cw, sy); |
597 | | - const seLL = pxToLatLng(sx + cw, sy + ch); |
| 606 | + const swLL = p2ll(sx, sy + sh); |
| 607 | + const nwLL = p2ll(sx, sy); |
| 608 | + const neLL = p2ll(sx + sw, sy); |
| 609 | + const seLL = p2ll(sx + sw, sy + sh); |
598 | 610 | return [swLL.lat, swLL.lng, nwLL.lat, nwLL.lng, neLL.lat, neLL.lng, seLL.lat, seLL.lng]; |
599 | 611 | } |
600 | 612 |
|
|
1133 | 1145 | const maxNativeZoom = zoomFromTableUsed |
1134 | 1146 | ? zDisplay |
1135 | 1147 | : Math.min(maxZoom, Math.max(minZoom, zDisplay - EXPORT_PDF_TILE_LEVELS_COARSER)); |
1136 | | - const layer = L.tileLayer(url, { |
1137 | | - bounds: bounds, |
| 1148 | + const layerOpts = { |
1138 | 1149 | minZoom, |
1139 | 1150 | maxZoom, |
1140 | 1151 | maxNativeZoom, |
1141 | 1152 | tms, |
1142 | 1153 | attribution: "Tiles", |
1143 | 1154 | ...TILE_LAYER_OPTIONS, |
1144 | | - }); |
| 1155 | + }; |
| 1156 | + if (!doCrop) layerOpts.bounds = bounds; |
| 1157 | + const layer = L.tileLayer(url, layerOpts); |
1145 | 1158 |
|
1146 | 1159 | setExportStatus("Loading map tiles for PDF…"); |
1147 | 1160 | await new Promise(function (resolve) { |
|
1224 | 1237 | * fitBounds keeps the whole LatLngBounds visible; Mercator vs container aspect often letterboxes, |
1225 | 1238 | * so getBounds() is larger than `bounds` and the raster would show extra map (wrong 1:scale vs orange extent). |
1226 | 1239 | */ |
| 1240 | + /** @type {{ sx: number; sy: number; sw: number; sh: number; preW: number; preH: number } | null} */ |
| 1241 | + let magneticCropOnOuter = null; |
| 1242 | + |
1227 | 1243 | if (!doCrop) { |
1228 | 1244 | const aabb = exportBoundsContainerPxAabb(printMap, bounds); |
1229 | 1245 | const loose = 1.5; |
|
1245 | 1261 | } |
1246 | 1262 | } |
1247 | 1263 | } |
1248 | | - } |
1249 | | - |
1250 | | - const sW = Math.round(W * h2cScale); |
1251 | | - const sH = Math.round(H * h2cScale); |
1252 | | - let sxCrop = 0; |
1253 | | - let syCrop = 0; |
1254 | | - if (doCrop && canvas.width >= sW && canvas.height >= sH) { |
1255 | | - sxCrop = Math.round((canvas.width - sW) / 2); |
1256 | | - syCrop = Math.round((canvas.height - sH) / 2); |
| 1264 | + } else if (declUsed !== null && Math.abs(declUsed) > 0.001) { |
| 1265 | + const aabb = exportBoundsContainerPxAabb(printMap, bounds); |
| 1266 | + const padL = (outerW - W) / 2; |
| 1267 | + const padT = (outerH - H) / 2; |
| 1268 | + const s = h2cScale; |
| 1269 | + let sxB = Math.floor((aabb.minX + padL) * s); |
| 1270 | + let syB = Math.floor((aabb.minY + padT) * s); |
| 1271 | + let swB = Math.max(1, Math.ceil(aabb.w * s)); |
| 1272 | + let shB = Math.max(1, Math.ceil(aabb.h * s)); |
| 1273 | + sxB = Math.max(0, Math.min(sxB, canvas.width - 2)); |
| 1274 | + syB = Math.max(0, Math.min(syB, canvas.height - 2)); |
| 1275 | + swB = Math.min(swB, canvas.width - sxB); |
| 1276 | + shB = Math.min(shB, canvas.height - syB); |
| 1277 | + if (swB > 4 && shB > 4) { |
| 1278 | + magneticCropOnOuter = { sx: sxB, sy: syB, sw: swB, sh: shB, preW: canvas.width, preH: canvas.height }; |
| 1279 | + const cMag = document.createElement("canvas"); |
| 1280 | + cMag.width = swB; |
| 1281 | + cMag.height = shB; |
| 1282 | + const ctxM = cMag.getContext("2d"); |
| 1283 | + if (ctxM) { |
| 1284 | + ctxM.drawImage(canvas, sxB, syB, swB, shB, 0, 0, swB, shB); |
| 1285 | + canvas = cMag; |
| 1286 | + } |
| 1287 | + } |
1257 | 1288 | } |
1258 | 1289 |
|
1259 | 1290 | let gptsForEmbed = gptsFromBoundsBox(bounds); |
1260 | | - if (doCrop && declUsed !== null && Math.abs(declUsed) > 0.001 && canvas.width >= sW && canvas.height >= sH) { |
1261 | | - gptsForEmbed = gptsFromMagneticCentreCrop( |
| 1291 | + if (magneticCropOnOuter && declUsed !== null && Math.abs(declUsed) > 0.001) { |
| 1292 | + const m = magneticCropOnOuter; |
| 1293 | + gptsForEmbed = gptsFromMagneticCropRect( |
1262 | 1294 | printMap, |
1263 | 1295 | declUsed, |
1264 | 1296 | W, |
1265 | 1297 | H, |
1266 | | - canvas.width, |
1267 | | - canvas.height, |
1268 | | - sxCrop, |
1269 | | - syCrop, |
| 1298 | + m.preW, |
| 1299 | + m.preH, |
| 1300 | + m.sx, |
| 1301 | + m.sy, |
| 1302 | + m.sw, |
| 1303 | + m.sh, |
1270 | 1304 | h2cScale, |
1271 | 1305 | ); |
1272 | 1306 | } |
1273 | 1307 |
|
1274 | | - if (doCrop && canvas.width >= sW && canvas.height >= sH) { |
1275 | | - const c2 = document.createElement("canvas"); |
1276 | | - c2.width = sW; |
1277 | | - c2.height = sH; |
1278 | | - const ctx2 = c2.getContext("2d"); |
1279 | | - if (ctx2) ctx2.drawImage(canvas, sxCrop, syCrop, sW, sH, 0, 0, sW, sH); |
1280 | | - canvas = c2; |
1281 | | - } |
1282 | | - |
1283 | 1308 | const prePdfEmbedW = canvas.width; |
1284 | 1309 | const prePdfEmbedH = canvas.height; |
1285 | 1310 | canvas = clampCanvasLongEdgeForJsPdf(canvas, EXPORT_JSPDF_MAX_IMAGE_LONG_EDGE_PX); |
|
1406 | 1431 |
|
1407 | 1432 | const pdfMag = document.getElementById("pdf-magnetic-north"); |
1408 | 1433 | if (pdfMag) { |
| 1434 | + pdfMag.checked = false; |
1409 | 1435 | const onPdfMagnetic = function () { |
1410 | 1436 | if (exportOutline && exportBounds) refreshExportOutline(); |
1411 | 1437 | }; |
|
0 commit comments