diff --git a/.gitignore b/.gitignore index 1fd56f0..e286fb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store .history node_modules/ +public/image/ dist/ npm-debug.log* yarn-debug.log* diff --git a/package.json b/package.json index bf170d8..1b6a2f4 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "ezuikit-js": "^8.1.10", "file-saver": "2.0.5", "fuse.js": "7.0.0", + "gcoord": "^1.0.7", "geotiff": "^2.1.4-beta.0", "highlight.js": "11.9.0", "image-conversion": "2.1.1", @@ -52,6 +53,7 @@ "nprogress": "0.2.0", "ol": "^10.5.0", "pinia": "2.2.6", + "proj4": "^2.19.3", "screenfull": "6.0.2", "spark-md5": "^3.0.2", "vue": "3.5.13", @@ -73,6 +75,7 @@ "@types/js-cookie": "3.0.6", "@types/node": "18.18.2", "@types/nprogress": "0.2.3", + "@types/proj4": "^2.5.6", "@unocss/preset-attributify": "0.64.1", "@unocss/preset-icons": "0.64.1", "@unocss/preset-uno": "0.64.1", diff --git a/public/image/clean_rgb_downsampled.tif b/public/image/clean_rgb_downsampled.tif new file mode 100644 index 0000000..86963bc Binary files /dev/null and b/public/image/clean_rgb_downsampled.tif differ diff --git a/public/image/convert_tif.bat b/public/image/convert_tif.bat new file mode 100644 index 0000000..9e00318 --- /dev/null +++ b/public/image/convert_tif.bat @@ -0,0 +1,55 @@ +@echo off +setlocal + +REM 源文件 +set SRC=odm_orthophoto.tif + +REM 中间文件:去掩膜 + 保留 4 波段 +set TMP1=tmp_nomask.tif + +REM 中间文件:gdalwarp 处理透明通道 +set TMP2=tmp_nomask_alpha.tif + +REM 最终输出文件(性能优化:缩放 + 压缩) +set FINAL=clean_no_mask_optimized.tif + +REM 配置参数 +REM -tr 0.25x 分辨率控制(0.25 倍像素,或者每个像素大小扩大4倍),视你项目需要可调 +REM -co 压缩参数:LZW 是无损压缩方式,TILED=YES 支持分块加载 +REM -r average 使用平均值重采样可保持图像质量 + +echo [1/4] gdal_translate:去除掩膜并保留RGBA四波段... +gdal_translate -b 1 -b 2 -b 3 -b 4 -mask none -co PHOTOMETRIC=RGB -co ALPHA=YES %SRC% %TMP1% +if errorlevel 1 ( + echo ❌ gdal_translate 失败 + exit /b 1 +) + +echo [2/4] gdalwarp:强制生成 Alpha 通道... +gdalwarp -dstalpha %TMP1% %TMP2% +if errorlevel 1 ( + echo ❌ gdalwarp 失败 + exit /b 1 +) + +echo [3/4] gdalwarp:降采样并启用压缩优化... +gdalwarp ^ + -r average ^ + -tr 2 2 ^ + -co COMPRESS=LZW ^ + -co TILED=YES ^ + -co PHOTOMETRIC=RGB ^ + -co ALPHA=YES ^ + %TMP2% %FINAL% +if errorlevel 1 ( + echo ❌ gdalwarp 压缩优化失败 + exit /b 1 +) + +echo [4/4] 清理临时文件... +del %TMP1% +del %TMP2% + +echo ✅ 完成!优化后的 GeoTIFF 已生成:%FINAL% +endlocal +pause diff --git a/src/api/safety/violationLevel/index.ts b/src/api/safety/violationLevel/index.ts new file mode 100644 index 0000000..1085314 --- /dev/null +++ b/src/api/safety/violationLevel/index.ts @@ -0,0 +1,63 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { ViolationLevelVO, ViolationLevelForm, ViolationLevelQuery } from '@/api/safety/violationLevel/types'; + +/** + * 查询违章等级列表 + * @param query + * @returns {*} + */ + +export const listViolationLevel = (query?: ViolationLevelQuery): AxiosPromise => { + return request({ + url: '/safety/violationLevel/list', + method: 'get', + params: query + }); +}; + +/** + * 查询违章等级详细 + * @param id + */ +export const getViolationLevel = (id: string | number): AxiosPromise => { + return request({ + url: '/safety/violationLevel/' + id, + method: 'get' + }); +}; + +/** + * 新增违章等级 + * @param data + */ +export const addViolationLevel = (data: ViolationLevelForm) => { + return request({ + url: '/safety/violationLevel', + method: 'post', + data: data + }); +}; + +/** + * 修改违章等级 + * @param data + */ +export const updateViolationLevel = (data: ViolationLevelForm) => { + return request({ + url: '/safety/violationLevel', + method: 'put', + data: data + }); +}; + +/** + * 删除违章等级 + * @param id + */ +export const delViolationLevel = (id: string | number | Array) => { + return request({ + url: '/safety/violationLevel/' + id, + method: 'delete' + }); +}; diff --git a/src/api/safety/violationLevel/types.ts b/src/api/safety/violationLevel/types.ts new file mode 100644 index 0000000..0e98d21 --- /dev/null +++ b/src/api/safety/violationLevel/types.ts @@ -0,0 +1,87 @@ +export interface ViolationLevelVO { + /** + * 违章等级 + */ + violationLevel: string; + id: string | number | Array; + /** + * 颜色 + */ + color: string; + + /** + * 风险等级 + */ + riskType: string; + + /** + * 违章类型(多个逗号分隔) + */ + violationType: string | Array; + + /** + * 创建时间 + */ + createTime: string; + postList?: Array; + postIdList?: Array; +} + +export interface ViolationLevelForm extends BaseEntity { + /** + * 主键id + */ + id?: string | number; + + /** + * 项目id + */ + projectId?: string | number; + + /** + * 违章等级 + */ + violationLevel?: string; + + /** + * 颜色 + */ + color?: string; + + /** + * 风险等级 + */ + riskType?: string; + + /** + * 违章类型(多个逗号分隔) + */ + violationType?: string; +} + +export interface ViolationLevelQuery extends PageQuery { + /** + * 项目id + */ + projectId?: string | number; + + /** + * 违章等级 + */ + violationLevel?: string; + + /** + * 风险等级 + */ + riskType?: string; + + /** + * 违章类型(多个逗号分隔) + */ + violationType?: string; + + /** + * 日期范围参数 + */ + params?: any; +} diff --git a/src/api/system/post/index.ts b/src/api/system/post/index.ts index 5f6ab18..6d05cdd 100644 --- a/src/api/system/post/index.ts +++ b/src/api/system/post/index.ts @@ -3,7 +3,7 @@ import { PostForm, PostQuery, PostVO } from './types'; import { AxiosPromise } from 'axios'; // 查询岗位列表 -export function listPost(query: PostQuery): AxiosPromise { +export function listPost(query: { pageNum: number; pageSize: number }): AxiosPromise { return request({ url: '/system/post/list', method: 'get', diff --git a/src/assets/images/orthophoto.jpg b/src/assets/images/orthophoto.jpg deleted file mode 100644 index b468004..0000000 Binary files a/src/assets/images/orthophoto.jpg and /dev/null differ diff --git a/src/assets/images/orthophoto.tif b/src/assets/images/orthophoto.tif deleted file mode 100644 index 7976c73..0000000 Binary files a/src/assets/images/orthophoto.tif and /dev/null differ diff --git a/src/utils/geoTiffLoader.ts b/src/utils/geoTiffLoader.ts new file mode 100644 index 0000000..23b34e6 --- /dev/null +++ b/src/utils/geoTiffLoader.ts @@ -0,0 +1,67 @@ +import { fromUrl } from 'geotiff'; // 假设你用的是这个包 +import { transform } from 'ol/proj'; +import { fromLonLat } from 'ol/proj'; +import ImageLayer from 'ol/layer/Image'; +import Static from 'ol/source/ImageStatic'; +import gcoord from 'gcoord'; + +export class GeoTiffLoader { + imageExtent: number[] = []; + imageLayer: ImageLayer | null = null; + tiffUrl: string; + + constructor(tiffUrl: string) { + this.tiffUrl = tiffUrl; + } + + async init() { + const tiff = await fromUrl(this.tiffUrl); + const image = await tiff.getImage(); + + const width = image.getWidth(); + const height = image.getHeight(); + const bbox = image.getBoundingBox(); // [minX, minY, maxX, maxY] + console.log('bbox', bbox); + + const rasters = (await image.readRasters({ interleave: true })) as Uint8Array; + + // 创建 Canvas + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d')!; + const imageData = ctx.createImageData(width, height); + + // 设置 RGBA 数据 + imageData.data.set(rasters); + ctx.putImageData(imageData, 0, 0); + + // 转成 Data URL + const imageUrl = canvas.toDataURL(); + + // 坐标转换 + const minLonLat = transform([bbox[0], bbox[1]], 'EPSG:32648', 'EPSG:4326'); + const maxLonLat = transform([bbox[2], bbox[3]], 'EPSG:32648', 'EPSG:4326'); + + // 转为 GCJ02 坐标系 + // 这里断言为[number, number, number],补0做三维坐标 + const gcjMin = gcoord.transform([minLonLat[0], minLonLat[1], 0], gcoord.WGS84, gcoord.GCJ02); + const gcjMax = gcoord.transform([maxLonLat[0], maxLonLat[1], 0], gcoord.WGS84, gcoord.GCJ02); + + // 再转为 EPSG:3857 + const minXY = fromLonLat(gcjMin); + const maxXY = fromLonLat(gcjMax); + + this.imageExtent = [...minXY, ...maxXY]; + + this.imageLayer = new ImageLayer({ + source: new Static({ + url: imageUrl, + imageExtent: this.imageExtent, + projection: 'EPSG:3857' + }) + }); + + console.log('imageExtent', this.imageExtent); + } +} diff --git a/src/views/progress/progressPaper/index.vue b/src/views/progress/progressPaper/index.vue index 370de03..25600e4 100644 --- a/src/views/progress/progressPaper/index.vue +++ b/src/views/progress/progressPaper/index.vue @@ -1,52 +1,54 @@ @@ -54,9 +56,9 @@ import Map from 'ol/Map'; // OpenLayers的主要类,用于创建和管理地图 import View from 'ol/View'; // OpenLayers的视图类,定义地图的视图属性 import { Tile as TileLayer, WebGLTile } from 'ol/layer'; // OpenLayers的瓦片图层类 -import { GeoTIFF, Raster, XYZ } from 'ol/source'; // OpenLayers的瓦片数据源,包括XYZ格式和OpenStreetMap专用的数据源 +import { Raster, XYZ } from 'ol/source'; // OpenLayers的瓦片数据源,包括XYZ格式和OpenStreetMap专用的数据源 import { defaults as defaultControls, defaults, FullScreen, MousePosition, ScaleLine } from 'ol/control'; -import { fromLonLat } from 'ol/proj'; +import { fromLonLat, toLonLat, transform, transformExtent } from 'ol/proj'; import { useUserStoreHook } from '@/store/modules/user'; import { getProjectSquare, listProgressCategory, addDaily, workScheduleListPosition } from '@/api/progress/plan'; import { ProgressCategoryVO, progressPlanDetailForm } from '@/api/progress/plan/types'; @@ -73,10 +75,13 @@ import { FeatureCollection, Geometry } from 'geojson'; import { MapViewFitter } from '@/utils/setMapCenter'; import PointerInteraction from 'ol/interaction/Pointer'; import { Coordinate } from 'ol/coordinate'; -import { fromBlob, fromUrl, fromArrayBuffer, Pool } from 'geotiff'; - +import GeoTIFF, { fromBlob, fromUrl, fromArrayBuffer, Pool } from 'geotiff'; import GeoTIFFSource from 'ol/source/GeoTIFF'; - +import ImageLayer from 'ol/layer/Image'; +import Static from 'ol/source/ImageStatic'; +import proj4 from 'proj4'; +import { register } from 'ol/proj/proj4'; +import gcoord from 'gcoord'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; const orthophoto = '@/assets/images/orthophoto.tif'; const selector = ref(null); @@ -100,6 +105,7 @@ const submitForm = ref({ finishedDetailIdList: [] as string[] }); const loading = ref(false); +const geoTiffLoading = ref(false); const matrixOptions = ref([]); const matrixValue = ref(matrixOptions.value.length > 0 ? matrixOptions.value[0].id : undefined); const progressCategoryList = ref([]); @@ -400,32 +406,55 @@ const getList = async () => { } }; +const imageExtent = ref(null); +const imageLayer = ref(null); +const initGeoTiff = async () => { + const tiff = await fromUrl('/image/clean_rgba_cleaned.tif'); + const image = await tiff.getImage(); + const width = image.getWidth(); + const height = image.getHeight(); + const bbox = image.getBoundingBox(); // [minX, minY, maxX, maxY] + console.log('bbox', bbox); + const rasters = await image.readRasters({ interleave: true }); + // 创建 Canvas + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d')!; + const imageData: any = ctx.createImageData(width, height); + // 设置 RGBA 数据 + imageData.data.set(rasters); // ✅ 完整设置,不用手动循环 + ctx.putImageData(imageData, 0, 0); + // 将 canvas 转成 Data URL 用作图层 source + const imageUrl = canvas.toDataURL(); + // 转换为 WGS84 经纬度 + const minLonLat = transform([bbox[0], bbox[1]], 'EPSG:32648', 'EPSG:4326'); + const maxLonLat = transform([bbox[2], bbox[3]], 'EPSG:32648', 'EPSG:4326'); + // 转为 GCJ02(高德地图坐标系) + const gcjMin = gcoord.transform(minLonLat as [number, number, number], gcoord.WGS84, gcoord.GCJ02); + const gcjMax = gcoord.transform(maxLonLat as [number, number, number], gcoord.WGS84, gcoord.GCJ02); + // 再转 EPSG:3857 供 OpenLayers 使用 + const minXY = fromLonLat(gcjMin); + const maxXY = fromLonLat(gcjMax); + + imageExtent.value = [...minXY, ...maxXY]; + + imageLayer.value = new ImageLayer({ + source: new Static({ + url: imageUrl, + imageExtent: imageExtent.value, + projection: 'EPSG:3857' + }) + }); + console.log('imageExtent', imageExtent.value); +}; + let map: any = null; const layerData = reactive({}); const centerPosition = ref(fromLonLat([107.12932403398425, 23.805564054229908])); const initOLMap = () => { - const source = new GeoTIFF({ - sources: [ - { - url: '../../../assets/images/orthophoto.tif' - } - ], - convertToRGB: true // 将色彩系统转换为RGB - }); - source.on('change', () => { - const state = source.getState(); - console.log('GeoTIFF 加载状态:', state); - if (state === 'ready') { - console.log('✅ GeoTIFF 加载成功'); - } else if (state === 'error') { - console.error('❌ GeoTIFF 加载失败'); - } - }); - const tiffLayer = new WebGLTile({ - source: source - }); - // 创造地图实例 + const borderLayer = createExtentBorderLayer(imageExtent.value); map = new Map({ // 设置地图容器的ID target: 'olMap', @@ -444,7 +473,8 @@ const initOLMap = () => { source: new XYZ({ url: 'http://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8' }) - }) + }), + imageLayer.value ], // 设置地图的视图参数 // View表示地图的视图,它定义了地图的中心点、缩放级别、旋转角度等参数。 @@ -463,12 +493,18 @@ const initOLMap = () => { zoom: false, rotate: false, attribution: false - }), + }).extend([new ScaleLine()]), interactions: defaultInteractions({ doubleClickZoom: false // 禁用双击缩放 }) }); map.on('click', (e: any) => { + var coordinate = e.coordinate; + + // 将投影坐标转换为经纬度坐标 + var lonLatCoordinate = toLonLat(coordinate); + // 输出转换后的经纬度坐标 + console.log('经纬度坐标:', lonLatCoordinate); const zoom = map.getView().getZoom(); const scale = Math.max(zoom / 10, 1); // 缩放比例,根据需要调整公式 map.forEachFeatureAtPixel(e.pixel, (feature: Feature) => { @@ -488,6 +524,12 @@ const initOLMap = () => { } }); }); + map.on('moveend', (e: any) => { + // console.log('地图移动', e); + // 获取当前缩放级别 + var zoomLevel = map.getView().getZoom(); + console.log('当前缩放级别:', zoomLevel); + }); // 4. 添加 pointermove 鼠标悬停事件 let lastFeature: Feature | null = null; @@ -542,6 +584,38 @@ const initOLMap = () => { }); }; +// 你已有的 imageExtent 是 [minX, minY, maxX, maxY] +const createExtentBorderLayer = (extent: number[]) => { + // 构造矩形坐标,闭合成环,顺序可以是顺时针或逆时针 + const coords = [ + [ + [extent[0], extent[1]], + [extent[0], extent[3]], + [extent[2], extent[3]], + [extent[2], extent[1]], + [extent[0], extent[1]] + ] + ]; + + const polygonFeature = new Feature(new Polygon(coords)); + + polygonFeature.setStyle( + new Style({ + stroke: new Stroke({ + color: 'red', // 你想要的边框颜色 + width: 3 // 线宽 + }), + fill: null // 不填充,纯边框 + }) + ); + + return new VectorLayer({ + source: new VectorSource({ + features: [polygonFeature] + }) + }); +}; + const highlightStyle = (name, scale) => { return new Style({ stroke: new Stroke({ @@ -714,9 +788,12 @@ const toggleFeatureHighlight = (feature: Feature, addIfNotExist = true) => { } }; -onMounted(() => { +onMounted(async () => { // 地图初始化 + geoTiffLoading.value = true; + await initGeoTiff(); initOLMap(); + geoTiffLoading.value = false; map.addLayer(sharedLayer); selector.value = new LassoSelector(map, sharedSource, (features, isInvert = false) => { features.forEach((feature) => { @@ -736,6 +813,7 @@ onMounted(() => { enableMiddleMousePan(map); getList(); + creatPoint(fromLonLat([107.13149145799198, 23.804125705140834]), 'Point', '1', '测试点1', '1'); }); function enableMiddleMousePan(map: Map) { @@ -758,9 +836,9 @@ function enableMiddleMousePan(map: Map) { map.addInteraction(middleButtonDragPan); // 禁用中键点击默认滚动行为(浏览器可能会出现滚动箭头) - map.getViewport().addEventListener('mousedown', (e) => { - if (e.button === 1) e.preventDefault(); - }); + // map.getViewport().addEventListener('mousedown', (e) => { + // if (e.button === 1) e.preventDefault(); + // }); } diff --git a/src/views/safety/violationLevel/component/add.vue b/src/views/safety/violationLevel/component/add.vue new file mode 100644 index 0000000..1e741d1 --- /dev/null +++ b/src/views/safety/violationLevel/component/add.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/src/views/safety/violationLevel/component/edit.vue b/src/views/safety/violationLevel/component/edit.vue new file mode 100644 index 0000000..736bed3 --- /dev/null +++ b/src/views/safety/violationLevel/component/edit.vue @@ -0,0 +1,204 @@ + + + + + diff --git a/src/views/safety/violationLevel/index.vue b/src/views/safety/violationLevel/index.vue new file mode 100644 index 0000000..cbf8d52 --- /dev/null +++ b/src/views/safety/violationLevel/index.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/vite.config.ts b/vite.config.ts index 0ce1ffd..e684f96 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,7 +15,7 @@ export default defineConfig(({ mode, command }: ConfigEnv): UserConfig => { '~': path.resolve(__dirname, './'), '@': path.resolve(__dirname, './src') }, - extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue', '.tif'] }, // https://cn.vitejs.dev/config/#resolve-extensions plugins: createPlugins(env, command === 'build'),