Files
jsswapp/app/src/main/assets/gis_map.html

451 lines
17 KiB
HTML
Raw Normal View History

2026-01-27 22:29:54 +08:00
<!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>