Files
jsswapp/app/src/main/assets/gis_map.html
Rue Ji b8472badf6 init
2026-01-27 22:29:54 +08:00

451 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>GIS管网管理</title>
<!-- OpenLayers CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v7.4.0/ol.css">
<style>
body, html { margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden; font-family: sans-serif; }
#map { width: 100%; height: 100%; }
/* UI Controls */
.ui-panel {
position: absolute;
top: 10px;
right: 10px;
background: rgba(255, 255, 255, 0.9);
padding: 10px;
border-radius: 4px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
z-index: 1000;
max-width: 200px;
}
.ui-group { margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 5px; }
.ui-group:last-child { border-bottom: none; }
.ui-title { font-weight: bold; font-size: 14px; margin-bottom: 5px; }
button {
display: block;
width: 100%;
margin: 2px 0;
padding: 5px;
font-size: 12px;
cursor: pointer;
background: #007bff;
color: white;
border: none;
border-radius: 2px;
}
button:hover { background: #0056b3; }
button.active { background: #004494; border: 2px solid #002a5c; }
select { width: 100%; padding: 5px; margin-bottom: 5px; }
/* Popup */
.ol-popup {
position: absolute;
background-color: white;
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
padding: 15px;
border-radius: 10px;
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
min-width: 200px;
}
.ol-popup:after, .ol-popup:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
.ol-popup-closer {
text-decoration: none;
position: absolute;
top: 2px;
right: 8px;
color: #999;
}
.ol-popup-closer:after { content: "✖"; }
/* Legend */
.legend {
position: absolute;
bottom: 20px;
left: 10px;
background: rgba(255,255,255,0.8);
padding: 10px;
border-radius: 4px;
z-index: 1000;
font-size: 12px;
}
.legend-item { display: flex; align-items: center; margin-bottom: 5px; }
.color-box { width: 20px; height: 5px; margin-right: 5px; }
.point-circle { width: 10px; height: 10px; border-radius: 50%; margin-right: 5px; }
</style>
</head>
<body>
<div id="map"></div>
<!-- Popup Overlay -->
<div id="popup" class="ol-popup">
<a href="#" id="popup-closer" class="ol-popup-closer"></a>
<div id="popup-content"></div>
</div>
<!-- UI Panel -->
<div class="ui-panel">
<div class="ui-group">
<div class="ui-title">底图切换</div>
<select id="baseLayerSelect" onchange="switchBaseLayer(this.value)">
<option value="OSM">OpenStreetMap</option>
<option value="GAODE">高德地图</option>
<option value="TIANDITU">天地图 (矢量)</option>
<option value="TIANDITU_IMG">天地图 (影像)</option>
</select>
</div>
<div class="ui-group">
<div class="ui-title">工具箱</div>
<button onclick="toggleMeasure('LineString')">测距</button>
<button onclick="toggleMeasure('Polygon')">测面</button>
<button onclick="clearMeasure()">清除测量</button>
</div>
<div class="ui-group">
<div class="ui-title">管网维护</div>
<button id="btn-add-pipe" onclick="startEdit('Pipe')">新增管线</button>
<button id="btn-add-station" onclick="startEdit('Station')">新增泵站</button>
<button onclick="stopEdit()">结束编辑</button>
<button onclick="deleteSelected()">删除选中</button>
</div>
<div class="ui-group">
<div class="ui-title">数据管理</div>
<button onclick="importCAD()">导入CAD (模拟)</button>
<button onclick="exportData()">导出数据</button>
</div>
</div>
<div class="legend">
<div class="ui-title">图例</div>
<div class="legend-item"><div class="color-box" style="background:blue;"></div>供水管线</div>
<div class="legend-item"><div class="color-box" style="background:red;"></div>污水管线</div>
<div class="legend-item"><div class="point-circle" style="background:green;"></div>正常泵站</div>
<div class="legend-item"><div class="point-circle" style="background:gray;"></div>离线泵站</div>
</div>
<!-- OpenLayers JS -->
<script src="https://cdn.jsdelivr.net/npm/ol@v7.4.0/dist/ol.js"></script>
<script>
// --- Configuration ---
// Note: In a real app, API keys should be secure or proxy-based.
const TIANDITU_KEY = 'YOUR_TIANDITU_KEY_HERE'; // Replace with valid key
// --- Map Initialization ---
const map = new ol.Map({
target: 'map',
view: new ol.View({
center: ol.proj.fromLonLat([121.47, 31.23]), // Shanghai
zoom: 12
}),
controls: ol.control.defaults.defaults().extend([
new ol.control.ScaleLine(),
new ol.control.FullScreen()
])
});
// --- Base Layers ---
const baseLayers = {
'OSM': new ol.layer.Tile({
source: new ol.source.OSM(),
visible: true
}),
'GAODE': new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
}),
visible: false
}),
'TIANDITU': new ol.layer.Tile({
source: new ol.source.XYZ({
url: `http://t{0-7}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${TIANDITU_KEY}`
}),
visible: false
}),
'TIANDITU_IMG': new ol.layer.Tile({
source: new ol.source.XYZ({
url: `http://t{0-7}.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${TIANDITU_KEY}`
}),
visible: false
})
};
for (let key in baseLayers) {
map.addLayer(baseLayers[key]);
}
function switchBaseLayer(type) {
for (let key in baseLayers) {
baseLayers[key].setVisible(key === type);
}
}
// --- Business Layers (Pipe & Station) ---
const vectorSource = new ol.source.Vector();
const vectorLayer = new ol.layer.Vector({
source: vectorSource,
style: function(feature) {
const type = feature.get('type');
const status = feature.get('status');
if (type === 'Pipe') {
return new ol.style.Style({
stroke: new ol.style.Stroke({
color: feature.get('pipeType') === 'Sewage' ? 'red' : 'blue',
width: 3
})
});
} else if (type === 'Station') {
return new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({ color: status === 'Online' ? 'green' : 'gray' }),
stroke: new ol.style.Stroke({ color: 'white', width: 2 })
}),
text: new ol.style.Text({
text: feature.get('name'),
offsetY: -15,
fill: new ol.style.Fill({ color: '#000' }),
stroke: new ol.style.Stroke({ color: '#fff', width: 2 })
})
});
}
}
});
map.addLayer(vectorLayer);
// --- Mock Data ---
function loadMockData() {
// Pipe
const pipe = new ol.Feature({
geometry: new ol.geom.LineString([
ol.proj.fromLonLat([121.46, 31.23]),
ol.proj.fromLonLat([121.48, 31.23])
]),
type: 'Pipe',
pipeType: 'Water',
id: 'P001'
});
// Station
const station = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat([121.47, 31.235])),
type: 'Station',
name: '1号泵站',
status: 'Online',
flow: '500m³/h',
level: '3.5m'
});
vectorSource.addFeatures([pipe, station]);
}
loadMockData();
// --- Interaction: Popup ---
const container = document.getElementById('popup');
const content = document.getElementById('popup-content');
const closer = document.getElementById('popup-closer');
const overlay = new ol.Overlay({
element: container,
autoPan: true,
autoPanAnimation: { duration: 250 }
});
map.addOverlay(overlay);
closer.onclick = function() {
overlay.setPosition(undefined);
closer.blur();
return false;
};
map.on('singleclick', function(evt) {
if (drawInteraction) return; // Don't popup when drawing
const feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) {
return feature;
});
if (feature) {
const coordinate = evt.coordinate;
const type = feature.get('type');
let html = '';
if (type === 'Station') {
html = `<b>${feature.get('name')}</b><br>
状态: ${feature.get('status')}<br>
流量: ${feature.get('flow')}<br>
液位: ${feature.get('level')}`;
} else if (type === 'Pipe') {
html = `<b>管线 ID: ${feature.get('id')}</b><br>
类型: ${feature.get('pipeType') === 'Sewage' ? '污水' : '供水'}`;
}
content.innerHTML = html;
overlay.setPosition(coordinate);
} else {
overlay.setPosition(undefined);
}
});
// --- Interaction: Draw & Modify ---
let drawInteraction;
const modifyInteraction = new ol.interaction.Modify({ source: vectorSource });
const selectInteraction = new ol.interaction.Select();
map.addInteraction(modifyInteraction);
map.addInteraction(selectInteraction);
function startEdit(type) {
stopEdit(); // Clear previous
let geometryType = type === 'Station' ? 'Point' : 'LineString';
drawInteraction = new ol.interaction.Draw({
source: vectorSource,
type: geometryType
});
drawInteraction.on('drawend', function(evt) {
const feature = evt.feature;
feature.set('type', type);
if (type === 'Station') {
feature.set('name', '新泵站');
feature.set('status', 'Online');
} else {
feature.set('pipeType', 'Water');
feature.set('id', 'New-' + Date.now());
}
});
map.addInteraction(drawInteraction);
// UI feedback
document.querySelectorAll('button').forEach(b => b.classList.remove('active'));
if(type === 'Pipe') document.getElementById('btn-add-pipe').classList.add('active');
if(type === 'Station') document.getElementById('btn-add-station').classList.add('active');
}
function stopEdit() {
if (drawInteraction) {
map.removeInteraction(drawInteraction);
drawInteraction = null;
}
document.querySelectorAll('button').forEach(b => b.classList.remove('active'));
}
function deleteSelected() {
const selectedFeatures = selectInteraction.getFeatures();
selectedFeatures.forEach(function(feature) {
vectorSource.removeFeature(feature);
});
selectedFeatures.clear();
overlay.setPosition(undefined);
}
// --- Tools: Measure ---
let measureDraw;
function toggleMeasure(type) {
stopEdit();
if (measureDraw) map.removeInteraction(measureDraw);
measureDraw = new ol.interaction.Draw({
source: new ol.source.Vector(),
type: type
});
measureDraw.on('drawstart', function(evt) {
// Simplified measurement logic for demo
// In real app, create tooltips and calculate length/area using ol.sphere
});
measureDraw.on('drawend', function(evt) {
const geom = evt.feature.getGeometry();
let output;
if (geom instanceof ol.geom.Polygon) {
output = '面积: ' + (geom.getArea() / 1000000).toFixed(2) + ' km²';
} else if (geom instanceof ol.geom.LineString) {
output = '距离: ' + (geom.getLength() / 1000).toFixed(2) + ' km';
}
alert(output);
map.removeInteraction(measureDraw); // Stop after one measure
});
map.addInteraction(measureDraw);
}
function clearMeasure() {
// Logic to clear measurement overlays would go here
if (measureDraw) map.removeInteraction(measureDraw);
}
// --- Data Import/Export ---
function importCAD() {
// Mock implementation
// In reality, this would trigger a file input, upload to server,
// server converts DWG -> GeoJSON, returns GeoJSON, then:
// const format = new ol.format.GeoJSON();
// const features = format.readFeatures(jsonData);
// vectorSource.addFeatures(features);
alert("模拟导入CAD图纸成功已转换为GeoJSON并加载。");
// Add a mock feature representing CAD import
const cadFeature = new ol.Feature({
geometry: new ol.geom.LineString([
ol.proj.fromLonLat([121.465, 31.225]),
ol.proj.fromLonLat([121.475, 31.225])
]),
type: 'Pipe',
pipeType: 'Sewage',
id: 'CAD-001'
});
vectorSource.addFeature(cadFeature);
}
function exportData() {
const format = new ol.format.GeoJSON();
const features = vectorSource.getFeatures();
const json = format.writeFeatures(features);
console.log(json);
alert("数据已导出 (查看控制台)");
// Can implement file download here
}
// --- Android Interface ---
// If loaded in Android WebView, this allows Android to call JS
window.locateDevice = function(lat, lon) {
const coords = ol.proj.fromLonLat([lon, lat]);
map.getView().animate({ center: coords, zoom: 15 });
// Add marker for user location
const userFeature = new ol.Feature({
geometry: new ol.geom.Point(coords),
type: 'Station', // Reuse station style or define new
name: '当前位置',
status: 'Online'
});
vectorSource.addFeature(userFeature);
};
</script>
</body>
</html>