ShapefileLoader.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. import * as THREE from "../../libs/three.js/build/three.module.js";
  2. import {Line2} from "../../libs/three.js/lines/Line2.js";
  3. import {LineGeometry} from "../../libs/three.js/lines/LineGeometry.js";
  4. import {LineMaterial} from "../../libs/three.js/lines/LineMaterial.js";
  5. import {LineDraw} from "../custom/utils/DrawUtil.js";
  6. /* 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")
  7. function transformCGCS2000ToWGS84([lng, lat]) {
  8. // Define the CGCS2000 and WGS84 coordinate systems
  9. const transformed = proj4("CGCS2000", "LOCAL", [lng, lat]);
  10. return transformed
  11. }
  12. */
  13. export class ShapefileLoader{
  14. constructor(){
  15. //this.transform = null;
  16. }
  17. async load(path, color){
  18. const matLine = new LineMaterial( {
  19. color: color || 0xff0000,
  20. lineWidth: 3, // in pixels
  21. resolution: new THREE.Vector2(1000, 1000),
  22. dashed: false
  23. } );
  24. const features = await this.loadShapefileFeatures(path);
  25. if(!features){
  26. console.error('no features', path)
  27. return
  28. }
  29. const node = new THREE.Object3D();
  30. let transform = await this.loadProj(path, node);
  31. if(!transform){
  32. transform = this.analyseTransform(features, path, node ) //自己根据数字来猜测,经纬度一般能准,但数字很大的话就不对了。用户自己移动吧
  33. }
  34. let jump = 0
  35. for(const feature of features){//5是碎的
  36. //if(feature.geometry.type!= 'MultiLineString' || ++jump != 5) continue
  37. const fnode = this.featureToSceneNode(feature, matLine, transform);
  38. fnode && node.add(fnode);
  39. }
  40. let setResolution = (x, y) => {
  41. matLine.resolution.set(x, y);
  42. };
  43. const result = {
  44. features: features,
  45. node: node,
  46. setResolution: setResolution,
  47. };
  48. return result;
  49. }
  50. featureToSceneNode(feature, matLine, transform){
  51. //console.log(feature)
  52. let geometry = feature.geometry;
  53. let color = new THREE.Color(1, 1, 1);
  54. if(!transform){
  55. transform = (v)=> v //{forward: (v) => v};
  56. }
  57. if(geometry.type === "Point"){
  58. let sg = new THREE.SphereGeometry(1, 18, 18);
  59. let sm = new THREE.MeshNormalMaterial();
  60. let s = new THREE.Mesh(sg, sm);
  61. let [long, lat] = geometry.coordinates;
  62. let pos = transform/* .forward */([long, lat]);
  63. s.position.set(...pos, 20);
  64. s.scale.set(10, 10, 10);
  65. return s;
  66. }else if(geometry.type === "LineString"){
  67. let coordinates = [];
  68. let min = new THREE.Vector3(Infinity, Infinity, Infinity);
  69. for(let i = 0; i < geometry.coordinates.length; i++){
  70. let [long, lat] = geometry.coordinates[i];
  71. let pos = transform/* .forward */([long, lat]);
  72. min.x = Math.min(min.x, pos[0]);
  73. min.y = Math.min(min.y, pos[1]);
  74. min.z = Math.min(min.z, 20);
  75. coordinates.push(...pos, 20);
  76. if(i > 0 && i < geometry.coordinates.length - 1){
  77. coordinates.push(...pos, 20);
  78. }
  79. }
  80. for(let i = 0; i < coordinates.length; i += 3){
  81. coordinates[i+0] -= min.x;
  82. coordinates[i+1] -= min.y;
  83. coordinates[i+2] -= min.z;
  84. }
  85. const lineGeometry = new LineGeometry();
  86. lineGeometry.setPositions( coordinates );
  87. const line = new Line2( lineGeometry, matLine );
  88. line.computeLineDistances();
  89. line.scale.set( 1, 1, 1 );
  90. line.position.copy(min);
  91. return line;
  92. }else if(geometry.type === "MultiLineString"){//xzw add 江门的那个文件
  93. let coordinates = [];
  94. //console.warn('MultiLineString') //多组连续线段,组之间不连续
  95. let min = new THREE.Vector3(Infinity, Infinity, Infinity);
  96. for(let i = 0; i < geometry.coordinates.length; i++){
  97. let points = geometry.coordinates[i]; //有时候是两个点有时候多个
  98. let coordinateSlice = []
  99. points.forEach(point=>{
  100. let [long, lat] = point;
  101. let pos = transform/* .forward */([long, lat]);
  102. min.x = Math.min(min.x, pos[0]);
  103. min.y = Math.min(min.y, pos[1]);
  104. min.z = Math.min(min.z, 20);
  105. coordinateSlice.push(new THREE.Vector3(pos[0], pos[1], 20));
  106. })
  107. coordinates.push(coordinateSlice)
  108. }
  109. coordinates.forEach(coordinateSlice=> coordinateSlice.forEach(point=>point.sub(min) ))
  110. let line = LineDraw.createFatLine(coordinates,{
  111. uncontinuous: true,
  112. mat:matLine
  113. /* color: 0xff0000,
  114. dashSize: 0.5,
  115. gapSize: 0.2,
  116. lineWidth: config$1.measure.lineWidth, */
  117. });
  118. line.position.copy(min);
  119. return line;
  120. }else if(geometry.type === "Polygon"){
  121. for(let pc of geometry.coordinates){
  122. let coordinates = [];
  123. let min = new THREE.Vector3(Infinity, Infinity, Infinity);
  124. for(let i = 0; i < pc.length; i++){
  125. let [long, lat] = pc[i];
  126. let pos = transform/* .forward */([long, lat]);
  127. min.x = Math.min(min.x, pos[0]);
  128. min.y = Math.min(min.y, pos[1]);
  129. min.z = Math.min(min.z, 20);
  130. coordinates.push(...pos, 20);
  131. if(i > 0 && i < pc.length - 1){
  132. coordinates.push(...pos, 20);
  133. }
  134. }
  135. for(let i = 0; i < coordinates.length; i += 3){
  136. coordinates[i+0] -= min.x;
  137. coordinates[i+1] -= min.y;
  138. coordinates[i+2] -= min.z;
  139. }
  140. const lineGeometry = new LineGeometry();
  141. lineGeometry.setPositions( coordinates );
  142. const line = new Line2( lineGeometry, matLine );
  143. line.computeLineDistances();
  144. line.scale.set( 1, 1, 1 );
  145. line.position.copy(min);
  146. return line;
  147. }
  148. }else{
  149. console.log("unhandled feature: ", geometry.type);
  150. }
  151. function getLine(coordinates){
  152. }
  153. }
  154. async loadShapefileFeatures(file){
  155. let features = [];
  156. if(file.split('.').pop() == 'shp'){
  157. let source = await shapefile.open(file);
  158. while(true){
  159. let result = await source.read();
  160. if (result.done) {
  161. break;
  162. }
  163. //value中的properties来自dbf,但该属性没有被用到,是否可以不加载
  164. if (result.value && result.value.type === 'Feature' && result.value.geometry !== undefined) {
  165. features.push(result.value);
  166. }
  167. }
  168. return features;
  169. }else if(file.split('.').pop() == 'json'){
  170. return new Promise((resolve,reject)=>{
  171. Potree.loadFile(file, { } , (data)=>{
  172. console.log(data);
  173. resolve(data.features)
  174. });
  175. })
  176. }
  177. }
  178. analyseTransform(features, path, node){//xzw只能自己暂时判断下 ,最好能根据.prj判断
  179. if(viewer.transform){
  180. let count = 0, maxCount = 100
  181. let notLonLat, transform
  182. let prjJson = node.prjJson
  183. if(prjJson && prjJson.unit ){
  184. if(prjJson.unit.name.toLowerCase() == 'meter' ){//'degree'
  185. notLonLat = true
  186. }
  187. }else{
  188. let judge = (coord)=>{
  189. if(!(coord[0] >= -180 && coord[0] <= 180 && coord[1] >= -90 && coord[1] <=90 )){//如果超出经纬度范围,那就不是经纬度
  190. notLonLat = true; node.prjNotSure = true //不确定,不设定默认位置
  191. }
  192. count++
  193. }//数字很小的话一般都是经纬度吧,就当作确定了
  194. for(let feature of features){
  195. if(count > maxCount)break
  196. for(let arr of feature.geometry.coordinates){
  197. if(arr[0] instanceof Array){
  198. for(let coord of arr){
  199. judge(coord)
  200. if(count > maxCount || notLonLat)break
  201. }
  202. }else{
  203. judge(arr)
  204. if(count > maxCount || notLonLat)break
  205. }
  206. }
  207. }
  208. }
  209. if(notLonLat){
  210. /* transform = (v)=>{
  211. let a = viewer.transform.lonlatTo4550.inverse(v) //这种类型和不使用transform很接近
  212. return viewer.transform.lonlatToLocal.forward(a)
  213. } */
  214. console.log('该shape不使用transform', path.split('/').pop())
  215. }else{
  216. transform = viewer.transform.lonlatToLocal.forward
  217. console.log('根据坐标数值猜测,该shape使用经纬度', path.split('/').pop())
  218. }
  219. return transform
  220. //loaders.shapeLoader.transform = viewer.transform.lonlatToLocal.forward;
  221. }else{
  222. node.prjNotSure = true
  223. console.log('该shape没用任何transform 因为场景没有设置经纬度', path.split('/').pop() )
  224. }
  225. }
  226. async loadProj(path, model){
  227. let a = path.split('/')
  228. let name = a.pop().split('.')[0]
  229. path = a.join('/') + '/' + name + '.prj';
  230. return new Promise((resolve,reject)=>{
  231. Potree.loadFile(path,{returnText:true},(e)=>{
  232. name += '_proj'
  233. let projDef = parsePrjToProj4(e, model)
  234. proj4.defs(name, projDef)
  235. console.log('loadedProj:', name, '解析得def: ', projDef, '原:', e, 'json:', model.prjJson)
  236. try{
  237. let proj = proj4(name, "LOCAL")
  238. let transform = ([lng, lat])=>{
  239. return proj.forward([lng, lat]);
  240. }
  241. resolve(transform)
  242. }catch(e){
  243. console.warn(e, '建立proj4 transform 失败', name )
  244. resolve()
  245. }
  246. },()=>{
  247. resolve()
  248. })
  249. })
  250. return transform
  251. }
  252. };
  253. function parsePrjToProj4(prjString, model) {//将prj的内容变为proj4的transform
  254. let def = ''
  255. let json = model.prjJson = prjToJson(prjString)
  256. let projectionType
  257. let datum = '';
  258. let ellipsoid = '';
  259. let centralLon = '0', centralLat = '0';
  260. let scaleFactor = '1';
  261. let falseEasting = '0', falseNorthing = '0';
  262. if(json.projection == 'Transverse_Mercator'){
  263. projectionType = 'tmerc'
  264. }else if(!json.projection){
  265. projectionType = 'longlat'
  266. }else{
  267. projectionType = projection
  268. console.log('unknown projection:', json.projection)
  269. }
  270. if(json.parameters){
  271. falseEasting = json.parameters.false_easting
  272. falseNorthing = json.parameters.false_northing
  273. centralLon = json.parameters.central_meridian
  274. centralLat = json.parameters.latitude_of_origin
  275. scaleFactor = json.parameters.scale_factor
  276. }
  277. if(json.geogcs?.spheroid){
  278. let spheroid = json.geogcs.spheroid.name.toLowerCase().replace('_', '')
  279. if(spheroid.includes('wgs84') || spheroid.includes('wgs1984') )ellipsoid = 'WGS84'
  280. }
  281. def += `+proj=${projectionType} `
  282. def += `+lat_0=${centralLat} `;
  283. def += `+lon_0=${centralLon} `;
  284. def += `+k=${scaleFactor} `;
  285. def += `+x_0=${falseEasting} `;
  286. def += `+y_0=${falseNorthing} `;
  287. ellipsoid && (def += `+ellps=${ellipsoid} `);
  288. def += '+units=m +no_defs';
  289. return def
  290. }
  291. function prjToJson(prjString) {//将prj的内容变为json
  292. const result = {};
  293. // 提取 PROJCS 和 GEOGCS 信息
  294. const projcsMatch = prjString.match(/PROJCS\["([^"]+)",(.*)\]/);
  295. if (projcsMatch) {
  296. result.projcsName = projcsMatch[1];
  297. const geoPart = projcsMatch[2];
  298. // 解析 GEOGCS 部分
  299. const geoMatch = geoPart.match(/GEOGCS\["([^"]+)",DATUM\["([^"]+)",SPHEROID\["([^"]+)",([0-9.]+),([0-9.]+)\]\],PRIMEM\["([^"]+)",([0-9.]+)\],UNIT\["([^"]+)",([0-9.]+)\]/);
  300. if (geoMatch) {
  301. result.geogcs = {
  302. name: geoMatch[1],
  303. datum: geoMatch[2],
  304. spheroid: {
  305. name: geoMatch[3],
  306. semiMajorAxis: parseFloat(geoMatch[4]),
  307. inverseFlattening: parseFloat(geoMatch[5])
  308. },
  309. primeMeridian: {
  310. name: geoMatch[6],
  311. value: parseFloat(geoMatch[7])
  312. },
  313. unit: {
  314. name: geoMatch[8],
  315. conversionFactor: parseFloat(geoMatch[9])
  316. }
  317. };
  318. }
  319. // 解析 PROJECTION 部分
  320. const projMatch = geoPart.match(/PROJECTION\["([^"]+)"\]/);
  321. if (projMatch) {
  322. result.projection = projMatch[1];
  323. }
  324. // 解析 PARAMETER 部分
  325. const parameters = {};
  326. const paramMatches = geoPart.matchAll(/PARAMETER\["([^"]+)",([-\d.]+)\]/g);
  327. for (const match of paramMatches) {
  328. parameters[match[1]] = parseFloat(match[2]);
  329. }
  330. result.parameters = parameters;
  331. // 解析 UNIT 部分
  332. const unitMatch = geoPart.match(/UNIT\["([^"]+)",([-\d.]+)\]/);
  333. if (unitMatch) {
  334. result.unit = {
  335. name: unitMatch[1],
  336. conversionFactor: parseFloat(unitMatch[2])
  337. };
  338. }
  339. } else {
  340. // 如果没有 PROJCS,则尝试解析 GEOGCS
  341. const geoMatch = prjString.match(/GEOGCS\["([^"]+)",DATUM\["([^"]+)",SPHEROID\["([^"]+)",([0-9.]+),([0-9.]+)\]\],PRIMEM\["([^"]+)",([0-9.]+)\],UNIT\["([^"]+)",([0-9.]+)\]/);
  342. if (geoMatch) {
  343. result.geogcs = {
  344. name: geoMatch[1],
  345. datum: geoMatch[2],
  346. spheroid: {
  347. name: geoMatch[3],
  348. semiMajorAxis: parseFloat(geoMatch[4]),
  349. inverseFlattening: parseFloat(geoMatch[5])
  350. },
  351. primeMeridian: {
  352. name: geoMatch[6],
  353. value: parseFloat(geoMatch[7])
  354. },
  355. unit: {
  356. name: geoMatch[8],
  357. conversionFactor: parseFloat(geoMatch[9])
  358. }
  359. };
  360. }
  361. }
  362. return result;
  363. }
  364. /*
  365. function parsePrjToProj4(prjString) {
  366. // 初始化 Proj4 字符串的部分
  367. let proj4Def = ''
  368. // 提取关键信息
  369. const prjParts = prjString.match(/(\w+)\[(.*?)\]/g);
  370. let projectionType = '';
  371. let datum = '';
  372. let ellipsoid = '';
  373. let centralMeridian = '';
  374. let scaleFactor = '1';
  375. let falseEasting = '0';
  376. let falseNorthing = '0';
  377. let latitudeOfOrigin = '0';
  378. prjParts.forEach(part => {
  379. if (part.includes('PROJECTION')) {
  380. projectionType = part.match(/"([^"]+)"/)[1].toLowerCase().replace('_', '');
  381. //xzw:
  382. if(projectionType == 'transversemercator') projectionType = 'tmerc'
  383. }else if (part.includes('DATUM')) {
  384. datum = part.match(/"([^"]+)"/)[1];
  385. } else if (part.includes('SPHEROID')) {
  386. ellipsoid = part.match(/"([^"]+)"/)[1].toLowerCase();
  387. } else if (part.includes('PARAMETER')) {
  388. let paramName = part.match(/"([^"]+)"/)[1].toLowerCase().replace(/ /g, '_');
  389. let paramValue = part.match(/(-?\d+\.?\d*)/)[1];
  390. switch (paramName) {
  391. case 'central_meridian':
  392. centralMeridian = paramValue;
  393. break;
  394. case 'scale_factor':
  395. scaleFactor = paramValue;
  396. break;
  397. case 'false_easting':
  398. falseEasting = paramValue;
  399. break;
  400. case 'false_northing':
  401. falseNorthing = paramValue;
  402. break;
  403. case 'latitude_of_origin':
  404. latitudeOfOrigin = paramValue;
  405. break;
  406. }
  407. }
  408. });
  409. // 构建完整的 Proj4 定义字符串
  410. if(!projectionType && prjString.includes('GCS_WGS_1984')) projectionType = 'longlat'
  411. projectionType && (proj4Def += `+proj=${projectionType} `)
  412. proj4Def += `+lat_0=${latitudeOfOrigin} `;
  413. proj4Def += `+lon_0=${centralMeridian} `;
  414. proj4Def += `+k=${scaleFactor} `;
  415. proj4Def += `+x_0=${falseEasting} `;
  416. proj4Def += `+y_0=${falseNorthing} `;
  417. ellipsoid && (proj4Def += `+ellps=${ellipsoid} `);
  418. proj4Def += '+units=m +no_defs';
  419. return proj4Def;
  420. }
  421. */
  422. /*
  423. function parsePrjToProj4(prjString) {
  424. // 初始化 Proj4 参数对象
  425. let proj4Params = {};
  426. // 提取 PROJCS 和 GEOGCS 部分
  427. const projcsMatch = prjString.match(/PROJCS\["([^"]+)",(.*)\]/);
  428. if (projcsMatch){
  429. const projcsName = projcsMatch[1];
  430. const geoPart = projcsMatch[2];
  431. // 解析 GEOGCS 部分
  432. const datumMatch = geoPart.match(/DATUM\["([^"]+)",SPHEROID\["([^"]+)",([0-9.]+),([0-9.]+)\]\]/);
  433. if (datumMatch) {
  434. proj4Params.datum = datumMatch[1].toLowerCase();
  435. proj4Params.ellps = datumMatch[2].toLowerCase();
  436. proj4Params.a = parseFloat(datumMatch[3]); // Semi-major axis
  437. proj4Params.rf = parseFloat(datumMatch[4]); // Inverse flattening
  438. }
  439. // 解析 PROJECTION 部分
  440. const projMatch = geoPart.match(/PROJECTION\["([^"]+)"\]/);
  441. if (projMatch) {
  442. proj4Params.proj = projMatch[1].toLowerCase().replace(/_/g, '');
  443. }
  444. // 解析 PARAMETER 部分
  445. const paramMatches = geoPart.matchAll(/PARAMETER\["([^"]+)",([-\d.]+)\]/g);
  446. for (const match of paramMatches) {
  447. const paramName = match[1].toLowerCase().replace(/ /g, '_');
  448. const paramValue = parseFloat(match[2]);
  449. switch (paramName) {
  450. case 'false_easting':
  451. proj4Params.x_0 = paramValue;
  452. break;
  453. case 'false_northing':
  454. proj4Params.y_0 = paramValue;
  455. break;
  456. case 'central_meridian':
  457. proj4Params.lon_0 = paramValue;
  458. break;
  459. case 'scale_factor':
  460. proj4Params.k = paramValue;
  461. break;
  462. case 'latitude_of_origin':
  463. proj4Params.lat_0 = paramValue;
  464. break;
  465. }
  466. }
  467. // 设置单位
  468. proj4Params.units = "m"; // 默认米
  469. proj4Params.no_defs = true;
  470. // 构建 Proj4 字符串
  471. let proj4Def = '+proj=' + proj4Params.proj;
  472. proj4Def += ' +lat_0=' + proj4Params.lat_0;
  473. proj4Def += ' +lon_0=' + proj4Params.lon_0;
  474. proj4Def += ' +k=' + proj4Params.k;
  475. proj4Def += ' +x_0=' + proj4Params.x_0;
  476. proj4Def += ' +y_0=' + proj4Params.y_0;
  477. proj4Def += ' +ellps=' + proj4Params.ellps;
  478. proj4Def += ' +units=' + proj4Params.units;
  479. proj4Def += ' +no_defs';
  480. return proj4Def;
  481. }
  482. */
  483. //shp必须,dbf目前没用,prj最好有