import * as THREE from "../../libs/three.js/build/three.module.js"; import {Line2} from "../../libs/three.js/lines/Line2.js"; import {LineGeometry} from "../../libs/three.js/lines/LineGeometry.js"; import {LineMaterial} from "../../libs/three.js/lines/LineMaterial.js"; import {LineDraw} from "../custom/utils/DrawUtil.js"; /* proj4.defs("CGCS2000","+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=WGS84 +units=m +no_defs") function transformCGCS2000ToWGS84([lng, lat]) { // Define the CGCS2000 and WGS84 coordinate systems const transformed = proj4("CGCS2000", "LOCAL", [lng, lat]); return transformed } */ export class ShapefileLoader{ constructor(){ //this.transform = null; } async load(path, color){ const matLine = new LineMaterial( { color: color || 0xff0000, lineWidth: 3, // in pixels resolution: new THREE.Vector2(1000, 1000), dashed: false } ); const features = await this.loadShapefileFeatures(path); if(!features){ console.error('no features', path) return } const node = new THREE.Object3D(); let transform = await this.loadProj(path, node); if(!transform){ transform = this.analyseTransform(features, path, node ) //自己根据数字来猜测,经纬度一般能准,但数字很大的话就不对了。用户自己移动吧 } let jump = 0 for(const feature of features){//5是碎的 //if(feature.geometry.type!= 'MultiLineString' || ++jump != 5) continue const fnode = this.featureToSceneNode(feature, matLine, transform); fnode && node.add(fnode); } let setResolution = (x, y) => { matLine.resolution.set(x, y); }; const result = { features: features, node: node, setResolution: setResolution, }; return result; } featureToSceneNode(feature, matLine, transform){ //console.log(feature) let geometry = feature.geometry; let color = new THREE.Color(1, 1, 1); if(!transform){ transform = (v)=> v //{forward: (v) => v}; } if(geometry.type === "Point"){ let sg = new THREE.SphereGeometry(1, 18, 18); let sm = new THREE.MeshNormalMaterial(); let s = new THREE.Mesh(sg, sm); let [long, lat] = geometry.coordinates; let pos = transform/* .forward */([long, lat]); s.position.set(...pos, 20); s.scale.set(10, 10, 10); return s; }else if(geometry.type === "LineString"){ let coordinates = []; let min = new THREE.Vector3(Infinity, Infinity, Infinity); for(let i = 0; i < geometry.coordinates.length; i++){ let [long, lat] = geometry.coordinates[i]; let pos = transform/* .forward */([long, lat]); min.x = Math.min(min.x, pos[0]); min.y = Math.min(min.y, pos[1]); min.z = Math.min(min.z, 20); coordinates.push(...pos, 20); if(i > 0 && i < geometry.coordinates.length - 1){ coordinates.push(...pos, 20); } } for(let i = 0; i < coordinates.length; i += 3){ coordinates[i+0] -= min.x; coordinates[i+1] -= min.y; coordinates[i+2] -= min.z; } const lineGeometry = new LineGeometry(); lineGeometry.setPositions( coordinates ); const line = new Line2( lineGeometry, matLine ); line.computeLineDistances(); line.scale.set( 1, 1, 1 ); line.position.copy(min); return line; }else if(geometry.type === "MultiLineString"){//xzw add 江门的那个文件 let coordinates = []; //console.warn('MultiLineString') //多组连续线段,组之间不连续 let min = new THREE.Vector3(Infinity, Infinity, Infinity); for(let i = 0; i < geometry.coordinates.length; i++){ let points = geometry.coordinates[i]; //有时候是两个点有时候多个 let coordinateSlice = [] points.forEach(point=>{ let [long, lat] = point; let pos = transform/* .forward */([long, lat]); min.x = Math.min(min.x, pos[0]); min.y = Math.min(min.y, pos[1]); min.z = Math.min(min.z, 20); coordinateSlice.push(new THREE.Vector3(pos[0], pos[1], 20)); }) coordinates.push(coordinateSlice) } coordinates.forEach(coordinateSlice=> coordinateSlice.forEach(point=>point.sub(min) )) let line = LineDraw.createFatLine(coordinates,{ uncontinuous: true, mat:matLine /* color: 0xff0000, dashSize: 0.5, gapSize: 0.2, lineWidth: config$1.measure.lineWidth, */ }); line.position.copy(min); return line; }else if(geometry.type === "Polygon"){ for(let pc of geometry.coordinates){ let coordinates = []; let min = new THREE.Vector3(Infinity, Infinity, Infinity); for(let i = 0; i < pc.length; i++){ let [long, lat] = pc[i]; let pos = transform/* .forward */([long, lat]); min.x = Math.min(min.x, pos[0]); min.y = Math.min(min.y, pos[1]); min.z = Math.min(min.z, 20); coordinates.push(...pos, 20); if(i > 0 && i < pc.length - 1){ coordinates.push(...pos, 20); } } for(let i = 0; i < coordinates.length; i += 3){ coordinates[i+0] -= min.x; coordinates[i+1] -= min.y; coordinates[i+2] -= min.z; } const lineGeometry = new LineGeometry(); lineGeometry.setPositions( coordinates ); const line = new Line2( lineGeometry, matLine ); line.computeLineDistances(); line.scale.set( 1, 1, 1 ); line.position.copy(min); return line; } }else{ console.log("unhandled feature: ", geometry.type); } function getLine(coordinates){ } } async loadShapefileFeatures(file){ let features = []; if(file.split('.').pop() == 'shp'){ let source = await shapefile.open(file); while(true){ let result = await source.read(); if (result.done) { break; } //value中的properties来自dbf,但该属性没有被用到,是否可以不加载 if (result.value && result.value.type === 'Feature' && result.value.geometry !== undefined) { features.push(result.value); } } return features; }else if(file.split('.').pop() == 'json'){ return new Promise((resolve,reject)=>{ Potree.loadFile(file, { } , (data)=>{ console.log(data); resolve(data.features) }); }) } } analyseTransform(features, path, node){//xzw只能自己暂时判断下 ,最好能根据.prj判断 if(viewer.transform){ let count = 0, maxCount = 100 let notLonLat, transform let prjJson = node.prjJson if(prjJson && prjJson.unit ){ if(prjJson.unit.name.toLowerCase() == 'meter' ){//'degree' notLonLat = true } }else{ let judge = (coord)=>{ if(!(coord[0] >= -180 && coord[0] <= 180 && coord[1] >= -90 && coord[1] <=90 )){//如果超出经纬度范围,那就不是经纬度 notLonLat = true; node.prjNotSure = true //不确定,不设定默认位置 } count++ }//数字很小的话一般都是经纬度吧,就当作确定了 for(let feature of features){ if(count > maxCount)break for(let arr of feature.geometry.coordinates){ if(arr[0] instanceof Array){ for(let coord of arr){ judge(coord) if(count > maxCount || notLonLat)break } }else{ judge(arr) if(count > maxCount || notLonLat)break } } } } if(notLonLat){ /* transform = (v)=>{ let a = viewer.transform.lonlatTo4550.inverse(v) //这种类型和不使用transform很接近 return viewer.transform.lonlatToLocal.forward(a) } */ console.log('该shape不使用transform', path.split('/').pop()) }else{ transform = viewer.transform.lonlatToLocal.forward console.log('根据坐标数值猜测,该shape使用经纬度', path.split('/').pop()) } return transform //loaders.shapeLoader.transform = viewer.transform.lonlatToLocal.forward; }else{ node.prjNotSure = true console.log('该shape没用任何transform 因为场景没有设置经纬度', path.split('/').pop() ) } } async loadProj(path, model){ let a = path.split('/') let name = a.pop().split('.')[0] path = a.join('/') + '/' + name + '.prj'; return new Promise((resolve,reject)=>{ Potree.loadFile(path,{returnText:true},(e)=>{ name += '_proj' let projDef = parsePrjToProj4(e, model) proj4.defs(name, projDef) console.log('loadedProj:', name, '解析得def: ', projDef, '原:', e, 'json:', model.prjJson) try{ let proj = proj4(name, "LOCAL") let transform = ([lng, lat])=>{ return proj.forward([lng, lat]); } resolve(transform) }catch(e){ console.warn(e, '建立proj4 transform 失败', name ) resolve() } },()=>{ resolve() }) }) return transform } }; function parsePrjToProj4(prjString, model) {//将prj的内容变为proj4的transform let def = '' let json = model.prjJson = prjToJson(prjString) let projectionType let datum = ''; let ellipsoid = ''; let centralLon = '0', centralLat = '0'; let scaleFactor = '1'; let falseEasting = '0', falseNorthing = '0'; if(json.projection == 'Transverse_Mercator'){ projectionType = 'tmerc' }else if(!json.projection){ projectionType = 'longlat' }else{ projectionType = projection console.log('unknown projection:', json.projection) } if(json.parameters){ falseEasting = json.parameters.false_easting falseNorthing = json.parameters.false_northing centralLon = json.parameters.central_meridian centralLat = json.parameters.latitude_of_origin scaleFactor = json.parameters.scale_factor } if(json.geogcs?.spheroid){ let spheroid = json.geogcs.spheroid.name.toLowerCase().replace('_', '') if(spheroid.includes('wgs84') || spheroid.includes('wgs1984') )ellipsoid = 'WGS84' } def += `+proj=${projectionType} ` def += `+lat_0=${centralLat} `; def += `+lon_0=${centralLon} `; def += `+k=${scaleFactor} `; def += `+x_0=${falseEasting} `; def += `+y_0=${falseNorthing} `; ellipsoid && (def += `+ellps=${ellipsoid} `); def += '+units=m +no_defs'; return def } function prjToJson(prjString) {//将prj的内容变为json const result = {}; // 提取 PROJCS 和 GEOGCS 信息 const projcsMatch = prjString.match(/PROJCS\["([^"]+)",(.*)\]/); if (projcsMatch) { result.projcsName = projcsMatch[1]; const geoPart = projcsMatch[2]; // 解析 GEOGCS 部分 const geoMatch = geoPart.match(/GEOGCS\["([^"]+)",DATUM\["([^"]+)",SPHEROID\["([^"]+)",([0-9.]+),([0-9.]+)\]\],PRIMEM\["([^"]+)",([0-9.]+)\],UNIT\["([^"]+)",([0-9.]+)\]/); if (geoMatch) { result.geogcs = { name: geoMatch[1], datum: geoMatch[2], spheroid: { name: geoMatch[3], semiMajorAxis: parseFloat(geoMatch[4]), inverseFlattening: parseFloat(geoMatch[5]) }, primeMeridian: { name: geoMatch[6], value: parseFloat(geoMatch[7]) }, unit: { name: geoMatch[8], conversionFactor: parseFloat(geoMatch[9]) } }; } // 解析 PROJECTION 部分 const projMatch = geoPart.match(/PROJECTION\["([^"]+)"\]/); if (projMatch) { result.projection = projMatch[1]; } // 解析 PARAMETER 部分 const parameters = {}; const paramMatches = geoPart.matchAll(/PARAMETER\["([^"]+)",([-\d.]+)\]/g); for (const match of paramMatches) { parameters[match[1]] = parseFloat(match[2]); } result.parameters = parameters; // 解析 UNIT 部分 const unitMatch = geoPart.match(/UNIT\["([^"]+)",([-\d.]+)\]/); if (unitMatch) { result.unit = { name: unitMatch[1], conversionFactor: parseFloat(unitMatch[2]) }; } } else { // 如果没有 PROJCS,则尝试解析 GEOGCS const geoMatch = prjString.match(/GEOGCS\["([^"]+)",DATUM\["([^"]+)",SPHEROID\["([^"]+)",([0-9.]+),([0-9.]+)\]\],PRIMEM\["([^"]+)",([0-9.]+)\],UNIT\["([^"]+)",([0-9.]+)\]/); if (geoMatch) { result.geogcs = { name: geoMatch[1], datum: geoMatch[2], spheroid: { name: geoMatch[3], semiMajorAxis: parseFloat(geoMatch[4]), inverseFlattening: parseFloat(geoMatch[5]) }, primeMeridian: { name: geoMatch[6], value: parseFloat(geoMatch[7]) }, unit: { name: geoMatch[8], conversionFactor: parseFloat(geoMatch[9]) } }; } } return result; } /* function parsePrjToProj4(prjString) { // 初始化 Proj4 字符串的部分 let proj4Def = '' // 提取关键信息 const prjParts = prjString.match(/(\w+)\[(.*?)\]/g); let projectionType = ''; let datum = ''; let ellipsoid = ''; let centralMeridian = ''; let scaleFactor = '1'; let falseEasting = '0'; let falseNorthing = '0'; let latitudeOfOrigin = '0'; prjParts.forEach(part => { if (part.includes('PROJECTION')) { projectionType = part.match(/"([^"]+)"/)[1].toLowerCase().replace('_', ''); //xzw: if(projectionType == 'transversemercator') projectionType = 'tmerc' }else if (part.includes('DATUM')) { datum = part.match(/"([^"]+)"/)[1]; } else if (part.includes('SPHEROID')) { ellipsoid = part.match(/"([^"]+)"/)[1].toLowerCase(); } else if (part.includes('PARAMETER')) { let paramName = part.match(/"([^"]+)"/)[1].toLowerCase().replace(/ /g, '_'); let paramValue = part.match(/(-?\d+\.?\d*)/)[1]; switch (paramName) { case 'central_meridian': centralMeridian = paramValue; break; case 'scale_factor': scaleFactor = paramValue; break; case 'false_easting': falseEasting = paramValue; break; case 'false_northing': falseNorthing = paramValue; break; case 'latitude_of_origin': latitudeOfOrigin = paramValue; break; } } }); // 构建完整的 Proj4 定义字符串 if(!projectionType && prjString.includes('GCS_WGS_1984')) projectionType = 'longlat' projectionType && (proj4Def += `+proj=${projectionType} `) proj4Def += `+lat_0=${latitudeOfOrigin} `; proj4Def += `+lon_0=${centralMeridian} `; proj4Def += `+k=${scaleFactor} `; proj4Def += `+x_0=${falseEasting} `; proj4Def += `+y_0=${falseNorthing} `; ellipsoid && (proj4Def += `+ellps=${ellipsoid} `); proj4Def += '+units=m +no_defs'; return proj4Def; } */ /* function parsePrjToProj4(prjString) { // 初始化 Proj4 参数对象 let proj4Params = {}; // 提取 PROJCS 和 GEOGCS 部分 const projcsMatch = prjString.match(/PROJCS\["([^"]+)",(.*)\]/); if (projcsMatch){ const projcsName = projcsMatch[1]; const geoPart = projcsMatch[2]; // 解析 GEOGCS 部分 const datumMatch = geoPart.match(/DATUM\["([^"]+)",SPHEROID\["([^"]+)",([0-9.]+),([0-9.]+)\]\]/); if (datumMatch) { proj4Params.datum = datumMatch[1].toLowerCase(); proj4Params.ellps = datumMatch[2].toLowerCase(); proj4Params.a = parseFloat(datumMatch[3]); // Semi-major axis proj4Params.rf = parseFloat(datumMatch[4]); // Inverse flattening } // 解析 PROJECTION 部分 const projMatch = geoPart.match(/PROJECTION\["([^"]+)"\]/); if (projMatch) { proj4Params.proj = projMatch[1].toLowerCase().replace(/_/g, ''); } // 解析 PARAMETER 部分 const paramMatches = geoPart.matchAll(/PARAMETER\["([^"]+)",([-\d.]+)\]/g); for (const match of paramMatches) { const paramName = match[1].toLowerCase().replace(/ /g, '_'); const paramValue = parseFloat(match[2]); switch (paramName) { case 'false_easting': proj4Params.x_0 = paramValue; break; case 'false_northing': proj4Params.y_0 = paramValue; break; case 'central_meridian': proj4Params.lon_0 = paramValue; break; case 'scale_factor': proj4Params.k = paramValue; break; case 'latitude_of_origin': proj4Params.lat_0 = paramValue; break; } } // 设置单位 proj4Params.units = "m"; // 默认米 proj4Params.no_defs = true; // 构建 Proj4 字符串 let proj4Def = '+proj=' + proj4Params.proj; proj4Def += ' +lat_0=' + proj4Params.lat_0; proj4Def += ' +lon_0=' + proj4Params.lon_0; proj4Def += ' +k=' + proj4Params.k; proj4Def += ' +x_0=' + proj4Params.x_0; proj4Def += ' +y_0=' + proj4Params.y_0; proj4Def += ' +ellps=' + proj4Params.ellps; proj4Def += ' +units=' + proj4Params.units; proj4Def += ' +no_defs'; return proj4Def; } */ //shp必须,dbf目前没用,prj最好有