| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
- <title>天地图</title>
-
- <!-- Leaflet CSS -->
- <link rel="stylesheet" href="./leaflet.css" />
- <!-- Leaflet MarkerCluster CSS -->
- <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
- <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css" />
-
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
-
- html, body {
- width: 100%;
- height: 100%;
- overflow: hidden;
- }
-
- #map {
- width: 100%;
- height: 100%;
- }
-
- /* 自定义标记点样式 */
- .custom-marker {
- display: flex;
- flex-direction: column;
- align-items: center;
- cursor: pointer;
- }
-
- .custom-marker-icon {
- width: 32px;
- height: 32px;
- object-fit: contain;
- }
-
- .custom-marker-label {
- margin-top: 2px;
- font-size: 12px;
- font-weight: bold;
- color: #000000;
- text-align: center;
- white-space: nowrap;
- /* 文字白色描边 */
- text-shadow:
- -1px -1px 0 #ffffff,
- 1px -1px 0 #ffffff,
- -1px 1px 0 #ffffff,
- 1px 1px 0 #ffffff,
- -1px 0 0 #ffffff,
- 1px 0 0 #ffffff,
- 0 -1px 0 #ffffff,
- 0 1px 0 #ffffff;
- }
-
- /* 位置标记点样式 - 圆形白底 */
- .location-marker {
- display: flex;
- flex-direction: column;
- align-items: center;
- cursor: pointer;
- }
-
- .location-marker-circle {
- width: 35px;
- height: 35px;
- background-color: #ffffff;
- border-radius: 50%;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- .location-marker-icon {
- width: 25px;
- height: 25px;
- object-fit: contain;
- }
- </style>
- </head>
- <body>
- <div id="map"></div>
-
- <!-- Leaflet JS -->
- <script src="./leaflet.js"></script>
- <!-- Leaflet MarkerCluster JS -->
- <script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js"></script>
-
- <script>
- console.log('=== Script started ===');
- console.log('window.location:', window.location.href);
-
- // 确保 Leaflet 完全加载后再初始化
- (function() {
- console.log('=== IIFE executed ===');
-
- // 等待 Leaflet 加载完成
- function initMap() {
- console.log('initMap called, typeof L:', typeof L);
-
- if (typeof L === 'undefined') {
- console.log('Waiting for Leaflet to load...');
- setTimeout(initMap, 100);
- return;
- }
-
- console.log('✓ Leaflet loaded, initializing map...');
-
- 'use strict';
-
- // 解析URL参数
- const urlParams = new URLSearchParams(window.location.search);
- const initialLat = parseFloat(urlParams.get('lat')) || 32.556211;
- const initialLng = parseFloat(urlParams.get('lng')) || 111.494811;
- const initialZoom = parseInt(urlParams.get('zoom')) || 13;
- const initialLayerType = urlParams.get('layer') || 'tianditu-img';
- const tiandituKey = urlParams.get('key') || '4c9c8a818d3cb25bf5ccbd749e7e67c8';
-
- // 天地图图层配置(使用固定子域名,避免 {s} 变量在鸿蒙系统的兼容性问题)
- const layerConfigs = {
- 'tianditu-vec': {
- tile: 'https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + tiandituKey,
- annotation: 'https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + tiandituKey,
- name: '天地图矢量'
- },
- 'tianditu-img': {
- tile: 'https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + tiandituKey,
- annotation: 'https://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + tiandituKey,
- name: '天地图影像'
- },
- 'tianditu-ter': {
- tile: 'https://t0.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + tiandituKey,
- annotation: 'https://t0.tianditu.gov.cn/cta_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cta&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + tiandituKey,
- name: '天地图地形'
- }
- };
-
- // 初始化地图
- console.log('Creating map with center:', initialLat, initialLng, 'zoom:', initialZoom);
- const map = L.map('map', {
- center: [initialLat, initialLng],
- zoom: initialZoom,
- zoomControl: false, // 禁用默认缩放控件
- attributionControl: false,
- preferCanvas: true // 使用 Canvas 渲染,性能更好
- });
- console.log('✓ Map created:', map);
-
- // 当前图层
- let currentLayerType = initialLayerType;
- let currentTileLayer = null;
- let currentAnnotationLayer = null;
- console.log('Initial layer type:', currentLayerType);
-
- // 标记点集合
- const markers = {};
-
- // 标记点聚合图层
- let markerClusterGroup = null;
-
- // 覆盖物集合
- const overlays = {
- polylines: [],
- circles: [],
- polygons: []
- };
-
- // 业务图层集合(用于叠加显示)
- const businessLayers = {};
-
- // 初始化标记点聚合图层
- function initMarkerCluster() {
- if (!markerClusterGroup) {
- markerClusterGroup = L.markerClusterGroup({
- maxClusterRadius: 80,
- spiderfyOnMaxZoom: true,
- showCoverageOnHover: false,
- zoomToBoundsOnClick: true
- });
- map.addLayer(markerClusterGroup);
- }
- }
-
- // 设置图层
- function setLayer(layerType) {
- const config = layerConfigs[layerType];
- if (!config) {
- console.error('Unknown layer type:', layerType);
- return;
- }
-
- // 移除旧图层
- if (currentTileLayer) {
- map.removeLayer(currentTileLayer);
- }
- if (currentAnnotationLayer) {
- map.removeLayer(currentAnnotationLayer);
- }
-
- // 添加新图层(不使用 subdomains,因为 URL 已是固定子域名)
- currentTileLayer = L.tileLayer(config.tile, {
- attribution: config.name,
- maxZoom: 18,
- minZoom: 3
- }).addTo(map);
-
- currentAnnotationLayer = L.tileLayer(config.annotation, {
- maxZoom: 18,
- minZoom: 3
- }).addTo(map);
-
- currentLayerType = layerType;
- }
-
- // 初始化图层
- setLayer(initialLayerType);
-
- // 处理图片路径,确保在所有平台都能正确显示
- function resolveImagePath(iconPath) {
- if (!iconPath) {
- return window.location.origin + '/static/images/map/project.png';
- }
-
- // 如果已经是完整 URL,直接返回
- if (iconPath.startsWith('http://') || iconPath.startsWith('https://') || iconPath.startsWith('data:') || iconPath.startsWith('file://')) {
- return iconPath;
- }
-
- // 如果是 /static/ 开头的路径
- if (iconPath.startsWith('/static/')) {
- // 判断运行环境
- const origin = window.location.origin;
- const protocol = window.location.protocol;
-
- // 如果是 file:// 协议(安卓原生 WebView)
- if (protocol === 'file:') {
- // 安卓 WebView 中,需要使用相对于 HTML 文件的相对路径
- // leaflet-map.html 在 /static/leaflet/ 目录
- // project.png 在 /static/images/map/ 目录
- // 所以相对路径是 ../images/map/project.png
- return iconPath.replace('/static/', '../');
- } else {
- // Web 环境,使用完整 URL
- return origin + iconPath;
- }
- }
-
- // 如果是相对路径,使用当前页面的 base URL
- const baseUrl = window.location.href.substring(0, window.location.href.lastIndexOf('/'));
- return baseUrl + '/' + iconPath.replace(/^\.\//, '');
- }
-
- // 添加标记点
- function addMarker(markerData) {
- // 初始化聚合图层
- initMarkerCluster();
-
- // 处理图片路径
- const resolvedIconPath = resolveImagePath(markerData.iconPath);
-
- // 检测是否为位置标记点
- const isLocationMarker = markerData.iconPath && markerData.iconPath.includes('location.png');
-
- let iconHtml = '';
- let iconSize = [32, 52];
- let iconAnchor = [16, 52];
- let popupAnchor = [0, -52];
-
- if (isLocationMarker) {
- // 位置标记点:圆形白底,图片在中间
- iconHtml = `
- <div class="location-marker">
- <div class="location-marker-circle">
- <img class="location-marker-icon" src="${resolvedIconPath}" alt="${markerData.title || ''}">
- </div>
- </div>
- `;
- iconSize = [48, 48];
- iconAnchor = [24, 24]; // 锚点在圆心
- popupAnchor = [0, -24];
- } else {
- // 普通标记点:图片在上,文字在下
- iconHtml = `
- <div class="custom-marker">
- <img class="custom-marker-icon" src="${resolvedIconPath}" alt="${markerData.title || ''}">
- <div class="custom-marker-label">${markerData.title || ''}</div>
- </div>
- `;
- iconSize = [markerData.width || 32, (markerData.height || 32) + 20];
- iconAnchor = [(markerData.width || 32) / 2, (markerData.height || 32) + 20];
- popupAnchor = [0, -((markerData.height || 32) + 20)];
- }
-
- const marker = L.marker([markerData.latitude, markerData.longitude], {
- icon: L.divIcon({
- html: iconHtml,
- className: '', // 移除默认样式
- iconSize: iconSize,
- iconAnchor: iconAnchor,
- popupAnchor: popupAnchor
- })
- });
-
- marker.bindPopup(markerData.title || '');
- marker.on('click', function() {
- postMessage({
- type: 'markerTap',
- markerId: markerData.id
- });
- });
-
- // 位置标记点不加入聚合图层
- if (isLocationMarker) {
- marker.addTo(map);
- } else {
- markerClusterGroup.addLayer(marker);
- }
-
- markers[markerData.id] = marker;
- }
-
- // 批量添加标记点
- function addMarkers(markersData) {
- initMarkerCluster();
-
- const markerArray = [];
- markersData.forEach(function(markerData) {
- // 处理图片路径
- const resolvedIconPath = resolveImagePath(markerData.iconPath);
-
- // 创建自定义 HTML 标记
- const iconHtml = `
- <div class="custom-marker">
- <img class="custom-marker-icon" src="${resolvedIconPath}" alt="${markerData.title || ''}">
- <div class="custom-marker-label">${markerData.title || ''}</div>
- </div>
- `;
-
- const marker = L.marker([markerData.latitude, markerData.longitude], {
- icon: L.divIcon({
- html: iconHtml,
- className: '', // 移除默认样式
- iconSize: [markerData.width || 32, (markerData.height || 32) + 20], // 高度增加以容纳文字
- iconAnchor: [(markerData.width || 32) / 2, (markerData.height || 32) + 20], // 锚点在底部
- popupAnchor: [0, -((markerData.height || 32) + 20)]
- })
- });
-
- marker.bindPopup(markerData.title || '');
- marker.on('click', function() {
- postMessage({
- type: 'markerTap',
- markerId: markerData.id
- });
- });
-
- markerArray.push(marker);
- markers[markerData.id] = marker;
- });
-
- // 批量添加到聚合图层,性能更好
- markerClusterGroup.addLayers(markerArray);
- }
-
- // 清除所有标记点
- function clearMarkers() {
- if (markerClusterGroup) {
- markerClusterGroup.clearLayers();
- }
- for (var id in markers) {
- delete markers[id];
- }
- }
-
- // 移除单个标记点
- function removeMarker(markerId) {
- if (markers[markerId]) {
- // 尝试从聚合图层移除
- if (markerClusterGroup && markerClusterGroup.hasLayer(markers[markerId])) {
- markerClusterGroup.removeLayer(markers[markerId]);
- } else {
- // 如果不在聚合图层(如位置标记点),直接从地图移除
- map.removeLayer(markers[markerId]);
- }
- delete markers[markerId];
- }
- }
-
- // 添加折线
- function addPolyline(data) {
- const latlngs = data.points.map(p => [p.latitude, p.longitude]);
- const polyline = L.polyline(latlngs, {
- color: data.color || '#007aff',
- weight: data.width || 4
- }).addTo(map);
-
- overlays.polylines.push(polyline);
- }
-
- // 添加圆形
- function addCircle(data) {
- const circle = L.circle([data.latitude, data.longitude], {
- radius: data.radius || 100,
- color: data.strokeColor || '#007aff',
- fillColor: data.fillColor || 'rgba(0, 122, 255, 0.2)',
- fillOpacity: 0.2,
- weight: 2
- }).addTo(map);
-
- overlays.circles.push(circle);
- }
-
- // 添加多边形
- function addPolygon(data) {
- const latlngs = data.points.map(p => [p.latitude, p.longitude]);
- const polygon = L.polygon(latlngs, {
- color: data.strokeColor || '#007aff',
- fillColor: data.fillColor || 'rgba(0, 122, 255, 0.2)',
- fillOpacity: 0.2,
- weight: 2
- }).addTo(map);
-
- overlays.polygons.push(polygon);
- }
-
- // 清除所有覆盖物
- function clearOverlays() {
- // 清除折线
- overlays.polylines.forEach(function(polyline) {
- map.removeLayer(polyline);
- });
- overlays.polylines = [];
-
- // 清除圆形
- overlays.circles.forEach(function(circle) {
- map.removeLayer(circle);
- });
- overlays.circles = [];
-
- // 清除多边形
- overlays.polygons.forEach(function(polygon) {
- map.removeLayer(polygon);
- });
- overlays.polygons = [];
- }
-
- // 设置地图中心
- function setCenter(lat, lng, zoom) {
- // zoom 可能为 null 或 undefined,都使用当前缩放级别
- const finalZoom = (zoom != null && zoom !== undefined) ? zoom : map.getZoom();
- // 禁用动画,避免安卓 WebView 闪白屏
- map.setView([lat, lng], finalZoom, {
- animate: false,
- duration: 0
- });
- }
-
- // 设置缩放级别
- function setZoom(zoom) {
- // 禁用动画,避免安卓 WebView 闪白屏
- map.setZoom(zoom, {
- animate: false
- });
- }
-
- // 切换图层
- function switchLayer(layerType) {
- setLayer(layerType);
- postMessage({
- type: 'layerChange',
- layerType: layerType
- });
- }
-
- // 添加业务图层
- function addBusinessLayer(layerCode, layerUrl, baseUrl, token) {
- // 如果图层已存在,先移除
- if (businessLayers[layerCode]) {
- map.removeLayer(businessLayers[layerCode]);
- }
-
- // 构建完整的 URL
- let fullUrl = baseUrl + layerUrl;
-
- // 添加 token 参数
- if (token && token.length > 0) {
- // 检查 URL 中是否已有查询参数
- const separator = fullUrl.indexOf('?') > -1 ? '&' : '?';
- fullUrl += separator + 'token=' + encodeURIComponent(token);
- }
-
- console.log('Adding business layer:', layerCode, fullUrl);
-
- // 创建图层
- const layer = L.tileLayer(fullUrl, {
- maxZoom: 18,
- minZoom: 3
- }).addTo(map);
-
- // 保存图层引用
- businessLayers[layerCode] = layer;
- }
-
- // 移除业务图层
- function removeBusinessLayer(layerCode) {
- const layer = businessLayers[layerCode];
- if (layer) {
- map.removeLayer(layer);
- delete businessLayers[layerCode];
- console.log('Removed business layer:', layerCode);
- }
- }
-
- // 向 uni-app 发送消息
- function postMessage(data) {
- console.log('Sending postMessage:', JSON.stringify(data));
- console.log('window.uni exists:', typeof window.uni !== 'undefined');
- console.log('window.uni.postMessage exists:', window.uni && typeof window.uni.postMessage !== 'undefined');
-
- if (window.uni && window.uni.postMessage) {
- window.uni.postMessage({
- data: data
- });
- console.log('postMessage sent successfully');
- } else {
- console.warn('window.uni.postMessage not available');
- }
- }
-
- // 监听地图事件
- map.on('moveend', function() {
- const center = map.getCenter();
- postMessage({
- type: 'regionChange',
- latitude: center.lat,
- longitude: center.lng,
- zoom: map.getZoom()
- });
- });
-
- map.on('zoomend', function() {
- const center = map.getCenter();
- postMessage({
- type: 'regionChange',
- latitude: center.lat,
- longitude: center.lng,
- zoom: map.getZoom()
- });
- });
-
- // 暴露全局方法供 uni-app 调用
- window.leafletMap = {
- addMarker: addMarker,
- addMarkers: addMarkers,
- clearMarkers: clearMarkers,
- removeMarker: removeMarker,
- addPolyline: addPolyline,
- addCircle: addCircle,
- addPolygon: addPolygon,
- clearOverlays: clearOverlays,
- setCenter: setCenter,
- setZoom: setZoom,
- switchLayer: switchLayer,
- addBusinessLayer: addBusinessLayer,
- removeBusinessLayer: removeBusinessLayer,
- getCenter: function() {
- const center = map.getCenter();
- return {
- latitude: center.lat,
- longitude: center.lng,
- zoom: map.getZoom()
- };
- }
- };
-
- // 地图加载完成
- console.log('Setting up map.whenReady callback...');
- map.whenReady(function() {
- console.log('Map is ready!');
- postMessage({
- type: 'ready',
- layerType: currentLayerType
- });
- });
-
- // 监听来自 uni-app 的消息
- window.addEventListener('message', function(e) {
- try {
- const message = e.data;
- if (!message || !message.type) return;
-
- switch(message.type) {
- case 'addMarker':
- if (message.data) addMarker(message.data);
- break;
- case 'addPolyline':
- if (message.data) addPolyline(message.data);
- break;
- case 'addCircle':
- if (message.data) addCircle(message.data);
- break;
- case 'addPolygon':
- if (message.data) addPolygon(message.data);
- break;
- case 'clearOverlays':
- clearOverlays();
- break;
- case 'setCenter':
- if (message.data) {
- setCenter(message.data.latitude, message.data.longitude, message.data.zoom);
- }
- break;
- case 'setZoom':
- if (message.data && message.data.zoom !== undefined) {
- setZoom(message.data.zoom);
- }
- break;
- case 'switchLayer':
- if (message.data && message.data.layerType) {
- switchLayer(message.data.layerType);
- }
- break;
- }
- } catch (error) {
- console.error('Error handling message:', error);
- postMessage({
- type: 'error',
- error: error.message
- });
- }
- });
-
- console.log('✓ Leaflet map initialized with layer:', currentLayerType);
- }
-
- // 启动初始化
- console.log('document.readyState:', document.readyState);
- if (document.readyState === 'loading') {
- console.log('Waiting for DOMContentLoaded...');
- document.addEventListener('DOMContentLoaded', function() {
- console.log('DOMContentLoaded fired, calling initMap');
- initMap();
- });
- } else {
- console.log('DOM already loaded, calling initMap directly');
- initMap();
- }
- })();
-
- console.log('=== Script end ===');
- </script>
- </body>
- </html>
|