Selaa lähdekoodia

fix: 前后端统一坐标转换,修复许愿树位置偏差

- 前端 MapView: 树坐标渲染前 GCJ-02 → WGS-84 转换
- 后端 CoordUtil: Java 版 WGS-84 ↔ GCJ-02 坐标转换工具
- 后端 WishTreeServiceImpl: 距离计算前用户 GPS 转 GCJ-02,与 DB 坐标系统一

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tanlie 3 viikkoa sitten
vanhempi
säilyke
2921a97e15

+ 5 - 1
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/service/impl/WishTreeServiceImpl.java

@@ -5,6 +5,7 @@ import cn.qinys.platform.mobile.mapper.WishingTreeMapper;
 import cn.qinys.platform.mobile.req.WishingTreeListReq;
 import cn.qinys.platform.mobile.resp.WishingTreeListResp;
 import cn.qinys.platform.mobile.service.WishTreeService;
+import cn.qinys.platform.mobile.util.CoordUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
@@ -39,10 +40,13 @@ public class WishTreeServiceImpl implements WishTreeService {
                         .eq(WishingTree::getIsActive, 1)
         );
 
+        // 用户 GPS 是 WGS-84,DB 存的是 GCJ-02,统一转为 GCJ-02 计算距离
+        double[] gcj = CoordUtil.wgs84ToGcj02(req.getLng().doubleValue(), req.getLat().doubleValue());
+
         List<WishingTreeListResp> result = new ArrayList<>();
         for (WishingTree tree : trees) {
             int dist = haversine(
-                    req.getLng().doubleValue(), req.getLat().doubleValue(),
+                    gcj[0], gcj[1],
                     tree.getLongitude().doubleValue(), tree.getLatitude().doubleValue()
             );
 

+ 47 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/util/CoordUtil.java

@@ -0,0 +1,47 @@
+package cn.qinys.platform.mobile.util;
+
+/**
+ * WGS-84 ↔ GCJ-02 坐标转换工具
+ */
+public class CoordUtil {
+
+    private static final double PI = 3.14159265358979324;
+    private static final double A = 6378245.0;
+    private static final double EE = 0.00669342162296594323;
+
+    private static boolean outOfChina(double lng, double lat) {
+        return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
+    }
+
+    private static double transformLat(double x, double y) {
+        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
+        ret += ((20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0) / 3.0;
+        ret += ((20.0 * Math.sin(y * PI) + 40.0 * Math.sin((y / 3.0) * PI)) * 2.0) / 3.0;
+        ret += ((160.0 * Math.sin((y / 12.0) * PI) + 320.0 * Math.sin((y * PI) / 30.0)) * 2.0) / 3.0;
+        return ret;
+    }
+
+    private static double transformLng(double x, double y) {
+        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
+        ret += ((20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0) / 3.0;
+        ret += ((20.0 * Math.sin(x * PI) + 40.0 * Math.sin((x / 3.0) * PI)) * 2.0) / 3.0;
+        ret += ((150.0 * Math.sin((x / 12.0) * PI) + 300.0 * Math.sin((x / 30.0) * PI)) * 2.0) / 3.0;
+        return ret;
+    }
+
+    /** WGS-84 → GCJ-02 */
+    public static double[] wgs84ToGcj02(double lng, double lat) {
+        if (outOfChina(lng, lat)) return new double[]{lng, lat};
+
+        double dLat = transformLat(lng - 105.0, lat - 35.0);
+        double dLng = transformLng(lng - 105.0, lat - 35.0);
+        double radLat = (lat / 180.0) * PI;
+        double magic = Math.sin(radLat);
+        magic = 1 - EE * magic * magic;
+        double sqrtMagic = Math.sqrt(magic);
+
+        double gcjLat = lat + (dLat * 180.0) / (((A * (1 - EE)) / (magic * sqrtMagic)) * PI);
+        double gcjLng = lng + (dLng * 180.0) / ((A / sqrtMagic) * Math.cos(radLat) * PI);
+        return new double[]{gcjLng, gcjLat};
+    }
+}

+ 4 - 35
wishing-tree-h5/src/views/MapView.vue

@@ -1,7 +1,6 @@
 <template>
   <div class="map-page with-bottom-nav">
     <div ref="mapContainer" class="map-container" />
-
     <!-- 定位按钮 -->
     <div class="map-controls">
       <van-button
@@ -15,7 +14,6 @@
         定位
       </van-button>
     </div>
-
     <!-- 许愿树信息弹出层 -->
     <van-popup
       v-model:show="showPopup"
@@ -71,7 +69,6 @@
     </van-popup>
   </div>
 </template>
-
 <script setup lang="ts">
 import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
 import { useRouter } from "vue-router";
@@ -80,43 +77,35 @@ import { useLocationStore } from "@/stores/location";
 import { getUserLocation } from "@/utils/geo";
 import { getTreeGradient, getTreeEmoji } from "@/utils/theme";
 import { fetchNearbyTrees } from "@/api/tree";
+import { gcj02ToWgs84 } from "@/utils/mapTool";
 import markerImg from "@/assets/location-marker.png";
 import L from "leaflet";
 import "leaflet/dist/leaflet.css";
-
 const router = useRouter();
 const locationStore = useLocationStore();
-
 const mapContainer = ref<HTMLDivElement>();
 const showPopup = ref(false);
 const selectedTree = ref<any>(null);
 const locating = ref(false);
-
 const popupGradient = computed(() =>
   getTreeGradient(selectedTree.value?.id || 1),
 );
 const popupEmoji = computed(() => getTreeEmoji(selectedTree.value?.id || 1));
-
 let map: any = null;
 let userMarker: any = null;
 let treeMarkers: L.Marker[] = [];
-
 function fmtDist(m: number) {
   return m >= 1000 ? (m / 1000).toFixed(1) + "km" : m + "m";
 }
-
 function goTreeDetail() {
   showPopup.value = false;
   router.push(`/tree/${selectedTree.value.id}`);
 }
-
 function goMakeWish() {
   showPopup.value = false;
   router.push({ path: "/make-wish", query: { treeId: selectedTree.value.id } });
 }
-
 // 高德瓦片(GCJ-02 坐标系)
-
 async function initMap() {
   try {
     // 尝试获取当前位置作为地图中心
@@ -129,7 +118,6 @@ async function initMap() {
     } catch {
       // 定位失败使用默认坐标
     }
-
     // 天地图参数
     const tileOptions = {
       zoomControl: true,
@@ -159,32 +147,26 @@ async function initMap() {
       zoomControl: false,
       attributionControl: false,
     });
-
     // L.tileLayer(GAODE_TILE, {
     //   subdomains: ["1", "2", "3", "4"],
     //   maxZoom: 18,
     // }).addTo(map);
     L.layerGroup().addTo(map);
-
     L.control.scale({ metric: true, imperial: false }).addTo(map);
     L.control.zoom({ position: "topright" }).addTo(map);
-
     locateUser();
   } catch (err) {
     showToast("地图加载失败");
     console.error(err);
   }
 }
-
 async function locateUser() {
   locating.value = true;
   try {
     const { lng, lat } = await getUserLocation();
     locationStore.setLocation(lng, lat);
-
     if (map) {
       map.setView([lat, lng], 18);
-
       if (userMarker) map.removeLayer(userMarker);
       const icon = L.icon({
         iconUrl: markerImg,
@@ -193,7 +175,6 @@ async function locateUser() {
       });
       userMarker = L.marker([lat, lng], { icon, zIndexOffset: 200 }).addTo(map);
     }
-
     await loadTrees();
   } catch {
     await loadTrees();
@@ -201,25 +182,20 @@ async function locateUser() {
     locating.value = false;
   }
 }
-
 async function loadTrees() {
   if (!locationStore.lng) return;
-
   const trees = await fetchNearbyTrees(
     locationStore.lng,
     locationStore.lat!,
     50000,
     100,
   );
-
   treeMarkers.forEach((m) => map.removeLayer(m));
   treeMarkers = [];
-
   trees.forEach((tree: any) => {
     const isIn = tree.isInRange;
     const color = isIn ? "#07c160" : "#ff976a";
     const label = `${tree.name} ${fmtDist(tree.distance)}`;
-
     const icon = L.divIcon({
       className: "",
       html: `
@@ -253,20 +229,16 @@ async function loadTrees() {
       iconSize: [0, 0] as any,
       iconAnchor: [0, 0],
     });
-
-    const marker = L.marker([tree.latitude, tree.longitude], { icon }).addTo(
-      map,
-    );
-
+    // DB 存储的是 GCJ-02 坐标,天地图 img_w 使用 WGS-84,需转换
+	    const wgs = gcj02ToWgs84({ lng: tree.longitude, lat: tree.latitude });
+	    const marker = L.marker([wgs.lat, wgs.lng], { icon }).addTo(map);
     marker.on("click", () => {
       selectedTree.value = tree;
       showPopup.value = true;
     });
-
     treeMarkers.push(marker);
   });
 }
-
 watch(
   () => [locationStore.lng, locationStore.lat] as const,
   ([lng, lat]) => {
@@ -275,19 +247,16 @@ watch(
     }
   },
 );
-
 onMounted(async () => {
   await nextTick();
   initMap();
   locationStore.startWatch(5000);
 });
-
 onUnmounted(() => {
   locationStore.stopWatch();
   if (map) map.remove();
 });
 </script>
-
 <style scoped>
 .map-page {
   position: relative;

BIN
wishing-tree-h5/wish.zip