znlgis 博客

GIS开发与技术分享

AI 水务前端开发指南

一、自定义开发

1. 添加新的地图图层

添加矢量图层

import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import GeoJSON from 'ol/format/GeoJSON'
import { Style, Stroke, Fill } from 'ol/style'

// 从 GeoJSON 文件加载
const vectorLayer = new VectorLayer({
  source: new VectorSource({
    url: '/data/your-data.geojson',
    format: new GeoJSON()
  }),
  style: new Style({
    stroke: new Stroke({
      color: '#3399CC',
      width: 2
    }),
    fill: new Fill({
      color: 'rgba(51, 153, 204, 0.3)'
    })
  }),
  properties: {
    name: '自定义图层',
    type: 'vector'
  }
})

// 添加到地图
mapInstance.addLayer(vectorLayer)

添加 WMS 图层

import TileLayer from 'ol/layer/Tile'
import TileWMS from 'ol/source/TileWMS'

const wmsLayer = new TileLayer({
  source: new TileWMS({
    url: '/geoserver/wms',
    params: {
      'LAYERS': 'workspace:layername',
      'TILED': true
    },
    serverType: 'geoserver'
  }),
  properties: {
    name: 'WMS 图层'
  }
})

mapInstance.addLayer(wmsLayer)

2. 自定义样式

创建样式函数

import { Style, Stroke, Fill, Circle, Icon, Text } from 'ol/style'

// 根据要素属性动态设置样式
const styleFunction = (feature) => {
  const type = feature.get('type')
  
  const styles = {
    water: new Style({
      fill: new Fill({ color: 'rgba(0, 100, 255, 0.5)' }),
      stroke: new Stroke({ color: '#0064FF', width: 2 })
    }),
    pipe: new Style({
      stroke: new Stroke({ color: '#FF6600', width: 3 })
    }),
    station: new Style({
      image: new Circle({
        radius: 8,
        fill: new Fill({ color: '#00FF00' }),
        stroke: new Stroke({ color: '#000', width: 2 })
      })
    })
  }
  
  return styles[type] || styles.water
}

vectorLayer.setStyle(styleFunction)

添加标注

const labelStyle = new Style({
  text: new Text({
    font: '14px Arial',
    text: feature.get('name'),
    fill: new Fill({ color: '#000' }),
    stroke: new Stroke({ color: '#fff', width: 2 }),
    offsetY: -20
  })
})

3. 地图交互

添加点击事件

import { Select } from 'ol/interaction'
import { click } from 'ol/events/condition'

// 创建选择交互
const selectInteraction = new Select({
  condition: click,
  style: new Style({
    stroke: new Stroke({ color: '#FF0000', width: 4 }),
    fill: new Fill({ color: 'rgba(255, 0, 0, 0.2)' })
  })
})

mapInstance.addInteraction(selectInteraction)

// 监听选择事件
selectInteraction.on('select', (e) => {
  const selectedFeatures = e.selected
  if (selectedFeatures.length > 0) {
    const feature = selectedFeatures[0]
    console.log('选中要素:', feature.getProperties())
  }
})

添加悬停效果

import { pointerMove } from 'ol/events/condition'

const hoverInteraction = new Select({
  condition: pointerMove,
  style: new Style({
    stroke: new Stroke({ color: '#00FF00', width: 3 })
  })
})

mapInstance.addInteraction(hoverInteraction)

4. 扩展 AI 功能

添加自定义指令处理

// services/aiCommandHandler.js
export function handleAICommand(response) {
  // 解析 AI 响应中的指令
  const commands = parseCommands(response)
  
  commands.forEach(cmd => {
    switch (cmd.type) {
      case 'zoom':
        mapInstance.getView().setZoom(cmd.level)
        break
      case 'center':
        mapInstance.getView().setCenter(fromLonLat(cmd.coords))
        break
      case 'addLayer':
        addLayerByName(cmd.layerName)
        break
      case 'highlight':
        highlightFeatures(cmd.filter)
        break
    }
  })
}

自定义 Dify 变量

sendChatMessage('查询水系', {
  inputs: {
    area: '北京市',
    dataType: 'water_system',
    format: 'geojson'
  },
  // ...其他选项
})

二、组件开发

1. 创建新面板组件

<!-- components/panels/MyPanel.vue -->
<template>
  <div class="my-panel">
    <div class="panel-header">
      <h3></h3>
    </div>
    <div class="panel-content">
      <slot></slot>
    </div>
  </div>
</template>

<script setup>
defineProps({
  title: {
    type: String,
    default: '面板'
  }
})
</script>

<style scoped>
.my-panel {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.panel-header {
  padding: 10px;
  background: #f0f0f0;
  border-bottom: 1px solid #ddd;
}

.panel-content {
  flex: 1;
  overflow: auto;
  padding: 10px;
}
</style>

2. 创建地图控件组件

<!-- components/controls/ZoomControl.vue -->
<template>
  <div class="zoom-control">
    <button @click="zoomIn">+</button>
    <span></span>
    <button @click="zoomOut">-</button>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { mapInstance } from '@/mapInstance'

const currentZoom = ref(10)

const zoomIn = () => {
  const view = mapInstance.getView()
  view.setZoom(view.getZoom() + 1)
}

const zoomOut = () => {
  const view = mapInstance.getView()
  view.setZoom(view.getZoom() - 1)
}

const updateZoom = () => {
  currentZoom.value = Math.round(mapInstance.getView().getZoom())
}

onMounted(() => {
  mapInstance.getView().on('change:resolution', updateZoom)
})

onUnmounted(() => {
  mapInstance.getView().un('change:resolution', updateZoom)
})
</script>

三、GeoServer 扩展

1. 添加新的 GeoServer 服务

// geoserver/FeatureService.js
import axios from 'axios'

const geoserverApi = axios.create({
  baseURL: '/geoserver'
})

// WFS 查询
export async function queryFeatures(typeName, filter) {
  const params = {
    service: 'WFS',
    version: '2.0.0',
    request: 'GetFeature',
    typeName,
    outputFormat: 'application/json'
  }
  
  if (filter) {
    params.CQL_FILTER = filter
  }
  
  const response = await geoserverApi.get('/wfs', { params })
  return response.data
}

// 空间查询
export async function queryByBbox(typeName, bbox) {
  const filter = `BBOX(geom, ${bbox.join(',')})`
  return queryFeatures(typeName, filter)
}

2. 图层样式管理

// geoserver/StyleService.js
export async function getStyles() {
  const response = await geoserverApi.get('/rest/styles.json')
  return response.data.styles?.style || []
}

export async function getStyleContent(styleName) {
  const response = await geoserverApi.get(`/rest/styles/${styleName}.sld`)
  return response.data
}

四、数据处理

1. GeoJSON 数据转换

// utils/geoUtils.js
import { transform } from 'ol/proj'

// 坐标转换
export function transformCoordinates(coords, from, to) {
  if (Array.isArray(coords[0])) {
    return coords.map(c => transformCoordinates(c, from, to))
  }
  return transform(coords, from, to)
}

// GeoJSON 投影转换
export function transformGeoJson(geojson, fromProj, toProj) {
  const transformed = JSON.parse(JSON.stringify(geojson))
  
  if (transformed.type === 'FeatureCollection') {
    transformed.features.forEach(feature => {
      feature.geometry.coordinates = transformCoordinates(
        feature.geometry.coordinates,
        fromProj,
        toProj
      )
    })
  } else if (transformed.type === 'Feature') {
    transformed.geometry.coordinates = transformCoordinates(
      transformed.geometry.coordinates,
      fromProj,
      toProj
    )
  }
  
  return transformed
}

2. 数据验证

// utils/validation.js
export function isValidGeoJSON(data) {
  if (!data || typeof data !== 'object') return false
  
  const validTypes = [
    'Point', 'LineString', 'Polygon',
    'MultiPoint', 'MultiLineString', 'MultiPolygon',
    'Feature', 'FeatureCollection'
  ]
  
  if (!validTypes.includes(data.type)) return false
  
  if (data.type === 'Feature') {
    return data.geometry && data.geometry.coordinates
  }
  
  if (data.type === 'FeatureCollection') {
    return Array.isArray(data.features)
  }
  
  return data.coordinates !== undefined
}

五、最佳实践

1. 组件设计原则

2. 性能优化

3. 错误处理

// 统一错误处理
export function handleError(error, context = '') {
  console.error(`[${context}]`, error)
  
  // 可以添加用户提示
  // showNotification({ type: 'error', message: error.message })
  
  // 可以上报错误
  // reportError(error, context)
}

4. 代码规范

六、学习资源

OpenLayers

GeoServer

Dify

Vue 3