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;});',
"",