diff --git a/fitsmap/cartographer.py b/fitsmap/cartographer.py index 4292dae..356c100 100644 --- a/fitsmap/cartographer.py +++ b/fitsmap/cartographer.py @@ -342,6 +342,7 @@ def build_conditional_js(out_dir: str, include_markerjs: bool) -> str: "js/labelControl.min.js", "js/settingsControl.min.js", "js/urlCoords.js", + "js/integerTranslate.min.js", "js/index.js", ] @@ -493,7 +494,9 @@ def build_index_js( loading_screen_js(image_layer_dicts), "", 'map.on("moveend", updateLocationBar);', + 'map.on("moveend", integerTranslateMapPane);', 'map.on("zoomend", updateLocationBar);', + 'map.on("zoomend", integerTranslateMapPane);', 'map.on("mousemove", (event) => {label.update(event.latlng);});', 'map.on("baselayerchange", (event) => {label.options.title = event.name;});', "", @@ -503,7 +506,7 @@ def build_index_js( "});", "", 'if (urlParam("zoom")==null) {', - f" map.fitBounds(L.latLngBounds([[0, 0], [{max_xy[0]}, {max_xy[1]}]]));", + f" map.fitBounds(L.latLngBounds([[0, 0], [{max_xy[1]}, {max_xy[0]}]]));", "} else {", " panFromUrl(map);", "}", diff --git a/fitsmap/support/integerTranslate.js b/fitsmap/support/integerTranslate.js new file mode 100644 index 0000000..5798088 --- /dev/null +++ b/fitsmap/support/integerTranslate.js @@ -0,0 +1,39 @@ +'use strict'; + +// Author: Mingyu Li (lmytime [at] hotmail.com) +// Date: 2024 Nov 11 +// Description: +// ==================== +// This script ensures that the x and y values of `translate3d` transformations are rounded to integers +// whenever the move or zoom action on the map ends. +// This helps to prevent the appearance of "white sub-pixel image borders" that occur due to floating point rendering +// inaccuracies, which is a common issue especially in Google Chrome. +// Note that this script can lead to slight shifts in the map position after a move or zoom operation, but it is generally acceptable. +// ==================== + +// Function to round the x and y components of the `translate3d` CSS transformation to integer values. +// This function is executed after the map ends a movement or zoom operation. +const integerTranslateMapPane = function (event) { + // Obtain the map pane element, which contains the current translation transformation information. + var mapPane = event.target.getPane('mapPane'); + var transformStyle = mapPane.style.transform; + + // Use a regular expression to extract the x, y, and z values from the `translate3d` transformation. + var xyzMatches = transformStyle.match(/translate3d\((-?\d+(\.\d+)?)px, (-?\d+(\.\d+)?)px, (-?\d+(\.\d+)?)px\)/); + + // If the `transform` style includes valid `translate3d` values, proceed with rounding the x and y. + if (xyzMatches) { + // Convert the matched x, y, and z values to floating point numbers, then round them to the nearest integer. + var xTranslateInt = Math.round(parseFloat(xyzMatches[1])); // Round the x component to the nearest integer + var yTranslateInt = Math.round(parseFloat(xyzMatches[3])); // Round the y component to the nearest integer + var zTranslateInt = Math.round(parseFloat(xyzMatches[5])); // Round the z component to the nearest integer (typically 0) + + // Update the `transform` style of the map pane to use the rounded x, y, and z values. + mapPane.style.transform = `translate3d(${xTranslateInt}px, ${yTranslateInt}px, ${zTranslateInt}px)`; + } +}; + +// Register event listeners on the map to execute the integer rounding function whenever the map ends a movement (`moveend`) +// or zoom operation (`zoomend`). This ensures that any slight inaccuracies from floating point values are corrected immediately. +// map.on("moveend", integerTranslateMapPane); +// map.on("zoomend", integerTranslateMapPane); \ No newline at end of file diff --git a/fitsmap/support/integerTranslate.min.js b/fitsmap/support/integerTranslate.min.js new file mode 100644 index 0000000..5aa8ec0 --- /dev/null +++ b/fitsmap/support/integerTranslate.min.js @@ -0,0 +1 @@ +"use strict";const integerTranslateMapPane=function(a){var t=a.target.getPane("mapPane"),r=t.style.transform.match(/translate3d\((-?\d+(\.\d+)?)px, (-?\d+(\.\d+)?)px, (-?\d+(\.\d+)?)px\)/);if(r){var e=Math.round(parseFloat(r[1])),n=Math.round(parseFloat(r[3])),s=Math.round(parseFloat(r[5]));t.style.transform=`translate3d(${e}px, ${n}px, ${s}px)`}}; \ No newline at end of file diff --git a/fitsmap/tests/data/expected_test_web.tar.xz b/fitsmap/tests/data/expected_test_web.tar.xz index d968631..0bb68b7 100644 Binary files a/fitsmap/tests/data/expected_test_web.tar.xz and b/fitsmap/tests/data/expected_test_web.tar.xz differ diff --git a/fitsmap/tests/data/expected_test_web_ellipse.tar.xz b/fitsmap/tests/data/expected_test_web_ellipse.tar.xz index a25c5a0..e25bda1 100644 Binary files a/fitsmap/tests/data/expected_test_web_ellipse.tar.xz and b/fitsmap/tests/data/expected_test_web_ellipse.tar.xz differ diff --git a/fitsmap/tests/data/expected_test_web_no_marker.tar.xz b/fitsmap/tests/data/expected_test_web_no_marker.tar.xz index 75c5525..80e39e2 100644 Binary files a/fitsmap/tests/data/expected_test_web_no_marker.tar.xz and b/fitsmap/tests/data/expected_test_web_no_marker.tar.xz differ diff --git a/fitsmap/tests/data/test_index.html b/fitsmap/tests/data/test_index.html index 598ae7e..ae446f1 100644 --- a/fitsmap/tests/data/test_index.html +++ b/fitsmap/tests/data/test_index.html @@ -22,6 +22,7 @@ + diff --git a/fitsmap/tests/data/test_index_wcs.html b/fitsmap/tests/data/test_index_wcs.html index 598ae7e..ae446f1 100644 --- a/fitsmap/tests/data/test_index_wcs.html +++ b/fitsmap/tests/data/test_index_wcs.html @@ -22,6 +22,7 @@ + diff --git a/fitsmap/tests/test_cartographer.py b/fitsmap/tests/test_cartographer.py index 621617b..6dd48a9 100644 --- a/fitsmap/tests/test_cartographer.py +++ b/fitsmap/tests/test_cartographer.py @@ -379,6 +379,7 @@ def test_build_conditional_js(): " ", " ", " ", + " ", " ", " ", " ", @@ -493,7 +494,9 @@ def test_build_index_js(): "});", "", 'map.on("moveend", updateLocationBar);', + 'map.on("moveend", integerTranslateMapPane);', 'map.on("zoomend", updateLocationBar);', + 'map.on("zoomend", integerTranslateMapPane);', 'map.on("mousemove", (event) => {label.update(event.latlng);});', 'map.on("baselayerchange", (event) => {label.options.title = event.name;});', "",