@mapcatch/util 1.0.15 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/catchUtil.min.esm.js +67984 -14011
  2. package/dist/catchUtil.min.js +2695 -55
  3. package/package.json +22 -3
  4. package/src/constants/annotation_color.js +7 -0
  5. package/src/constants/annotation_draw_style.js +228 -0
  6. package/src/constants/annotation_label_style.js +76 -0
  7. package/src/constants/annotation_style.js +118 -0
  8. package/src/constants/cameras.js +1 -1
  9. package/src/constants/crs.js +31473 -31473
  10. package/src/constants/error_codes.js +44 -0
  11. package/src/constants/height_colors.js +1 -0
  12. package/src/constants/index.js +9 -2
  13. package/src/constants/map_style.js +11 -0
  14. package/src/constants/measurement_fields.js +3 -3
  15. package/src/{event.js → event/event.js} +1 -14
  16. package/src/event/event_bus.js +5 -0
  17. package/src/event/index.js +2 -0
  18. package/src/gl-operations/constants.js +9 -11
  19. package/src/gl-operations/default_options.js +5 -5
  20. package/src/gl-operations/index.js +166 -239
  21. package/src/gl-operations/reglCommands/contours.js +20 -20
  22. package/src/gl-operations/reglCommands/default.js +34 -34
  23. package/src/gl-operations/reglCommands/hillshading.js +116 -116
  24. package/src/gl-operations/reglCommands/index.js +6 -6
  25. package/src/gl-operations/reglCommands/multiLayers.js +55 -55
  26. package/src/gl-operations/reglCommands/transitions.js +24 -24
  27. package/src/gl-operations/reglCommands/util.js +54 -54
  28. package/src/gl-operations/renderer.js +69 -69
  29. package/src/gl-operations/shaders/transform.js +2 -2
  30. package/src/gl-operations/shaders/util/rgbaToFloat.glsl +11 -11
  31. package/src/gl-operations/texture_manager.js +58 -58
  32. package/src/gl-operations/util.js +154 -154
  33. package/src/index.js +14 -2
  34. package/src/measure/index.js +198 -0
  35. package/src/measure/tile_cache.js +88 -0
  36. package/src/mvs/index.js +26 -0
  37. package/src/mvs/protos/index.js +12 -0
  38. package/src/mvs/protos/proto_10.js +155 -0
  39. package/src/observation_pretict.js +168 -0
  40. package/src/photo-parser/exif/gps_tags.js +33 -0
  41. package/src/photo-parser/exif/ifd1_tags.js +22 -0
  42. package/src/photo-parser/exif/index.js +130 -0
  43. package/src/photo-parser/exif/parse_image.js +290 -0
  44. package/src/photo-parser/exif/string_values.js +137 -0
  45. package/src/photo-parser/exif/tags.js +75 -0
  46. package/src/photo-parser/exif/tiff_tags.js +35 -0
  47. package/src/photo-parser/exif/util.js +103 -0
  48. package/src/photo-parser/image-size/detector.js +24 -0
  49. package/src/photo-parser/image-size/fromFile.js +55 -0
  50. package/src/photo-parser/image-size/index.js +2 -0
  51. package/src/photo-parser/image-size/lookup.js +37 -0
  52. package/src/photo-parser/image-size/types/bmp.js +10 -0
  53. package/src/photo-parser/image-size/types/cur.js +16 -0
  54. package/src/photo-parser/image-size/types/dds.js +10 -0
  55. package/src/photo-parser/image-size/types/gif.js +11 -0
  56. package/src/photo-parser/image-size/types/heif.js +35 -0
  57. package/src/photo-parser/image-size/types/icns.js +112 -0
  58. package/src/photo-parser/image-size/types/ico.js +74 -0
  59. package/src/photo-parser/image-size/types/index.js +43 -0
  60. package/src/photo-parser/image-size/types/j2c.js +11 -0
  61. package/src/photo-parser/image-size/types/jp2.js +22 -0
  62. package/src/photo-parser/image-size/types/jpg.js +157 -0
  63. package/src/photo-parser/image-size/types/ktx.js +18 -0
  64. package/src/photo-parser/image-size/types/png.js +36 -0
  65. package/src/photo-parser/image-size/types/pnm.js +74 -0
  66. package/src/photo-parser/image-size/types/psd.js +10 -0
  67. package/src/photo-parser/image-size/types/svg.js +100 -0
  68. package/src/photo-parser/image-size/types/tga.js +14 -0
  69. package/src/photo-parser/image-size/types/tiff.js +92 -0
  70. package/src/photo-parser/image-size/types/utils.js +83 -0
  71. package/src/photo-parser/image-size/types/webp.js +67 -0
  72. package/src/photo-parser/index.js +181 -0
  73. package/src/report/annotations_report.js +446 -0
  74. package/src/report/index.js +2 -0
  75. package/src/report/map_util.js +81 -0
  76. package/src/report/pdf_creator.js +247 -0
  77. package/src/report/report.js +583 -0
  78. package/src/transform.js +204 -0
  79. package/src/util.js +371 -75
  80. package/CHANGELOG.md +0 -60
  81. /package/src/constants/{colors.js → dsm_colors.js} +0 -0
@@ -0,0 +1,181 @@
1
+ import {getPhotoMetadata} from './exif'
2
+ import moment from 'moment'
3
+ import * as util from '../util'
4
+ import _ from 'lodash'
5
+
6
+ export async function parsePhoto (file) {
7
+ let tags = await getPhotoMetadata(file)
8
+ const {width, height, exif, xmp, filesize} = tags
9
+ let imageMetaData = {width, height, filesize}
10
+ let lng = NaN, lat = NaN, alt = NaN
11
+ let yaw = NaN, pitch = NaN, roll = NaN
12
+ let x_std = NaN, y_std = NaN, z_std = NaN
13
+ let pos = [lng, lat, alt]
14
+ let flatedXmp = util.flatObject(xmp)
15
+ let {GPSAltitude, GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef} = exif
16
+ if (GPSLongitude && GPSLatitude) {
17
+ pos = [
18
+ util.getGeoCoordOperator(GPSLongitudeRef) * util.toRadian(GPSLongitude),
19
+ util.getGeoCoordOperator(GPSLatitudeRef) * util.toRadian(GPSLatitude),
20
+ +GPSAltitude
21
+ ]
22
+ }
23
+ for(let key in flatedXmp) {
24
+ let value = flatedXmp[key]
25
+ if (key === 'drone-dji:GpsLongtitude' || key === 'drone-dji:GpsLongitude') {
26
+ lng = Number(value)
27
+ } else if (key === 'drone-dji:GpsLatitude') {
28
+ lat = Number(value)
29
+ } else if (key === 'drone-dji:AbsoluteAltitude') {
30
+ alt = Number(value)
31
+ } else if (key === 'drone-dji:GimbalYawDegree') {
32
+ yaw = Number(value)
33
+ } else if (key === 'drone-dji:GimbalPitchDegree') {
34
+ pitch = Number(value)
35
+ } else if (key === 'drone-dji:GimbalRollDegree') {
36
+ roll = Number(value)
37
+ } else if (key === 'drone-dji:RtkStdLon') {
38
+ x_std = Number(value)
39
+ } else if (key === 'drone-dji:RtkStdLat') {
40
+ y_std = Number(value)
41
+ } else if (key === 'drone-dji:RtkStdHgt') {
42
+ z_std = Number(value)
43
+ } else if (key === 'drone-dji:DewarpFlag') {
44
+ imageMetaData.dewarp_flag = Boolean(Number(value))
45
+ } else if (key === 'drone-dji:RtkFlag') {
46
+ imageMetaData.rtk_flag = Number(value)
47
+ } else if (key === 'drone-dji:RelativeAltitude') {
48
+ imageMetaData.relative_altitude = Number(value)
49
+ } else if (key === 'tiff:Model') {
50
+ imageMetaData.camera_model = value
51
+ } else if (key === 'drone-dji:DewarpData') {
52
+ let params = value.split(';')[1].split(',')
53
+ let pre_calib_param = params.map(d => Number(d))
54
+ pre_calib_param[2] = width / 2 + pre_calib_param[2]
55
+ pre_calib_param[3] = height / 2 + pre_calib_param[3]
56
+ imageMetaData.pre_calib_param = pre_calib_param
57
+ } else if (key === 'drone-dji:CaptureUUID') {
58
+ imageMetaData.capture_uuid = value
59
+ } else if (key === 'drone-dji:DroneID') {
60
+ imageMetaData.drone_id = value
61
+ }
62
+ }
63
+ for(let key in flatedXmp) {
64
+ let value = flatedXmp[key]
65
+ if (key === 'drone-dji:GpsStatus' && value === 'RTK' && Number.isNaN(x_std)) {
66
+ x_std = 0.02
67
+ y_std = 0.02
68
+ } else if (key === 'drone-dji:AltitudeType' && value === 'RtkAlt' && Number.isNaN(z_std)) {
69
+ z_std = 0.02
70
+ }
71
+ }
72
+ if (!Number.isNaN(x_std) && !Number.isNaN(y_std) && !Number.isNaN(z_std)) {
73
+ imageMetaData.pos_std = [x_std, y_std, z_std]
74
+ }
75
+ if (!Number.isNaN(yaw) && !Number.isNaN(pitch) && !Number.isNaN(roll)) {
76
+ imageMetaData.pitch_roll_yaw = [pitch, roll, yaw]
77
+ imageMetaData.orientation = util.getRotationMatrix(pitch, roll, yaw)
78
+ }
79
+ if (exif.DateTimeOriginal) {
80
+ imageMetaData.capture_time = moment(exif.DateTimeOriginal, 'YYYY:MM:DD HH:mm:ss').valueOf()
81
+ }
82
+ if (exif.FocalLengthIn35mmFilm) {
83
+ imageMetaData.focal_length_in_35mm = exif.FocalLengthIn35mmFilm
84
+ }
85
+ if (util.hasNaN(pos)) {
86
+ pos = [lng, lat, alt]
87
+ }
88
+ if (!util.hasNaN(pos)) {
89
+ imageMetaData.pos = pos
90
+ imageMetaData.absolute_altitude = pos[2]
91
+ imageMetaData.coordinate_system = {
92
+ type: 2,
93
+ label: 'WGS 84',
94
+ type_name: 'Geographic',
95
+ epsg_code: 4326
96
+ }
97
+ if (!util.hasNaN([x_std, y_std, z_std])) { // 估算pos_sigma
98
+ let pos_sigma = []
99
+ let accuracy = Math.sqrt(Math.pow(x_std, 2) + Math.pow(y_std, 2))
100
+ if (accuracy < 0.03) {
101
+ pos_sigma[0] = pos_sigma[1] = 0.03
102
+ } else if (accuracy < 0.2) {
103
+ pos_sigma[0] = pos_sigma[1] = 0.2
104
+ } else {
105
+ pos_sigma[0] = pos_sigma[1] = 2.0
106
+ }
107
+
108
+ if (z_std < 0.03) {
109
+ pos_sigma[2] = 0.06
110
+ } else if (z_std < 0.2) {
111
+ pos_sigma[2] = 0.5
112
+ } else {
113
+ pos_sigma[2] = 5.0
114
+ }
115
+ imageMetaData.pos_sigma = pos_sigma
116
+ } else {
117
+ imageMetaData.pos_sigma = [2, 2, 5]
118
+ }
119
+ }
120
+ return imageMetaData
121
+ }
122
+
123
+ export function parsePhotos (files, cb) {
124
+ let cameras = []
125
+ let processQueue = files
126
+ let total = files.length
127
+ let concurrence = 20 // 并发数
128
+ let request = 0 // 当前正在解析的照片数
129
+
130
+ return new Promise((resolve) => {
131
+ const run = () => {
132
+ for(let i = request;i < concurrence;i++) {
133
+ let file = processQueue.pop()
134
+ if (!file) {
135
+ break
136
+ }
137
+ request++
138
+ this.parsePhoto(file).then(meta_data => {
139
+ request--
140
+ let ext = file.split('.').pop()
141
+ let camera = {
142
+ width: meta_data.width,
143
+ height: meta_data.height,
144
+ ext,
145
+ parameters: meta_data.pre_calib_param || null
146
+ }
147
+ let item = cameras.find(d => {
148
+ return d.width === camera.width && d.height === camera.height && d.ext === camera.ext && _.isEqual(d.parameters, camera.parameters)
149
+ })
150
+ if (!item) {
151
+ item = camera
152
+ item.fileList = []
153
+ cameras.push(item)
154
+ }
155
+ item.fileList.push({
156
+ name: file.split('\\').pop(),
157
+ filePath: file,
158
+ meta_data
159
+ })
160
+ done()
161
+ }).catch(err => {
162
+ request--
163
+ done()
164
+ console.error(err)
165
+ })
166
+ }
167
+ }
168
+ const done = () => {
169
+ if (processQueue.length) {
170
+ run()
171
+ } else if (!request) {
172
+ resolve(cameras)
173
+ }
174
+ let parsed = total - processQueue.length - request
175
+ if (cb) {
176
+ cb(parsed, total)
177
+ }
178
+ }
179
+ run()
180
+ })
181
+ }
@@ -0,0 +1,446 @@
1
+ import _ from 'lodash'
2
+ import { formatDate, getAnnotationFeature } from '../util'
3
+ import {mapStyle, layerFolders} from '../constants'
4
+ import * as turf from '@turf/turf'
5
+ import mapboxgl from 'mapbox-gl'
6
+ mapboxgl.accessToken =
7
+ 'pk.eyJ1Ijoid2FueWFueWFuIiwiYSI6Im1uNVZnTncifQ.90XY40_yjpItUHO8HnbbpA'
8
+
9
+ export async function generateReport (params) {
10
+ let {projectInfo, taskInfo, annotations} = params
11
+ return {
12
+ title: '标注报告',
13
+ projectName: projectInfo.name,
14
+ taskName: taskInfo.name,
15
+ user_id: taskInfo.user_id,
16
+ project_id: taskInfo.project_id,
17
+ task_id: taskInfo.task_id,
18
+ data_type: taskInfo.data_type,
19
+ sections: [
20
+ {
21
+ title: '任务概览',
22
+ content: [
23
+ {
24
+ type: 'form',
25
+ items: getTaskPreview(taskInfo)
26
+ },
27
+ {
28
+ type: 'image',
29
+ imageType: 'overview'
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ title: '标注列表',
35
+ content: [
36
+ {
37
+ type: 'sub-title',
38
+ label: '点标注'
39
+ },
40
+ {
41
+ type: 'annotation-list',
42
+ colums: getPointColums(taskInfo.data_type),
43
+ data: getAnnoData(annotations, 'point')
44
+ },
45
+ {
46
+ type: 'sub-title',
47
+ label: '线标注'
48
+ },
49
+ {
50
+ type: 'annotation-list',
51
+ colums: getLineColums(taskInfo.data_type),
52
+ data: getAnnoData(annotations, 'line')
53
+ },
54
+ {
55
+ type: 'sub-title',
56
+ label: '面标注'
57
+ },
58
+ {
59
+ type: 'annotation-list',
60
+ colums: getPolygonColums(taskInfo.data_type),
61
+ data: getAnnoData(annotations, 'polygon')
62
+ },
63
+ {
64
+ type: 'page-line' // 分页
65
+ }
66
+ ]
67
+ },
68
+ {
69
+ title: '标注详情',
70
+ content: [
71
+ {
72
+ type: 'details',
73
+ data: getAnnotationDetails(annotations, taskInfo)
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ }
79
+ }
80
+
81
+ function getTaskPreview (taskInfo) {
82
+ return [
83
+ {
84
+ label: '任务名称',
85
+ value: taskInfo.name
86
+ },
87
+ {
88
+ label: '任务类型',
89
+ value: getDataType(taskInfo.data_type)
90
+ },
91
+ {
92
+ label: '数据采集时间',
93
+ value: formatDate(taskInfo.captured_at)
94
+ },
95
+ {
96
+ label: '重建时间',
97
+ value: formatDate(taskInfo.completed_at)
98
+ }
99
+ ]
100
+ }
101
+
102
+ function getDataType (type) {
103
+ if (type === 'infrared') {
104
+ return '热红外'
105
+ } else if (type === 'multispectral') {
106
+ return '多光谱'
107
+ } else {
108
+ return '可见光'
109
+ }
110
+ }
111
+
112
+ function getPointColums (data_type) {
113
+ return [
114
+ {
115
+ title: '',
116
+ slot: 'color',
117
+ key: 'color',
118
+ width: 100
119
+ },
120
+ {
121
+ title: '名称',
122
+ key: 'name'
123
+ },
124
+ {
125
+ title: '经度(X)',
126
+ key: 'x'
127
+ },
128
+ {
129
+ title: '纬度(Y)',
130
+ key: 'y'
131
+ },
132
+ {
133
+ title: data_type === 'infrared' ? '温度(℃)' : '高度(Z)',
134
+ key: data_type === 'infrared' ? 'temprature' : 'height'
135
+ }
136
+ ]
137
+ }
138
+
139
+ function getLineColums (data_type) {
140
+ if (data_type === 'infrared') {
141
+ return [
142
+ {
143
+ title: '',
144
+ slot: 'color',
145
+ key: 'color',
146
+ width: 100
147
+ },
148
+ {
149
+ title: '名称',
150
+ key: 'name'
151
+ },
152
+ {
153
+ title: '长度(m)',
154
+ key: 'distance2d'
155
+ },
156
+ {
157
+ title: '最低温度(℃)',
158
+ key: 'temprature_min'
159
+ },
160
+ {
161
+ title: '最高温度(℃)',
162
+ key: 'temprature_max'
163
+ }
164
+ ]
165
+ } else {
166
+ return [
167
+ {
168
+ title: '',
169
+ slot: 'color',
170
+ key: 'color',
171
+ width: 100
172
+ },
173
+ {
174
+ title: '名称',
175
+ key: 'name'
176
+ },
177
+ {
178
+ title: '2D长度(m)',
179
+ key: 'distance2d'
180
+ },
181
+ {
182
+ title: '3D长度(m)',
183
+ key: 'distance3d'
184
+ },
185
+ {
186
+ title: '最小坡度(°)',
187
+ key: 'slope_min'
188
+ },
189
+ {
190
+ title: '最大坡度(°)',
191
+ key: 'slope_max'
192
+ },
193
+ {
194
+ title: '最小高度(m)',
195
+ key: 'height_min'
196
+ },
197
+ {
198
+ title: '最大高度(m)',
199
+ key: 'height_max'
200
+ }
201
+ ]
202
+ }
203
+ }
204
+
205
+ function getPolygonColums (data_type) {
206
+ if (data_type === 'infrared') {
207
+ return [
208
+ {
209
+ title: '',
210
+ slot: 'color',
211
+ key: 'color',
212
+ width: 100
213
+ },
214
+ {
215
+ title: '名称',
216
+ key: 'name'
217
+ },
218
+ {
219
+ title: '面积(㎡)',
220
+ key: 'area2d'
221
+ },
222
+ {
223
+ title: '周长(m)',
224
+ key: 'distance2d'
225
+ },
226
+ {
227
+ title: '最低温度(℃)',
228
+ key: 'temprature_min'
229
+ },
230
+ {
231
+ title: '最高温度(℃)',
232
+ key: 'temprature_max'
233
+ }
234
+ ]
235
+ } else {
236
+ return [
237
+ {
238
+ title: '',
239
+ slot: 'color',
240
+ key: 'color',
241
+ width: 100
242
+ },
243
+ {
244
+ title: '名称',
245
+ key: 'name'
246
+ },
247
+ {
248
+ title: '2D面积(㎡)',
249
+ key: 'area2d'
250
+ },
251
+ {
252
+ title: '3D面积(㎡)',
253
+ key: 'area3d'
254
+ },
255
+ {
256
+ title: '2D周长(m)',
257
+ key: 'distance2d'
258
+ },
259
+ {
260
+ title: '3D周长(m)',
261
+ key: 'distance3d'
262
+ },
263
+ {
264
+ title: '最小高度(m)',
265
+ key: 'height_min'
266
+ },
267
+ {
268
+ title: '最大高度(m)',
269
+ key: 'height_max'
270
+ }
271
+ ]
272
+ }
273
+ }
274
+
275
+ function getAnnoData (annotations, type) {
276
+ return annotations.filter(d => d.type === type).map(d => {
277
+ let obj = _.cloneDeep(d)
278
+ if (type === 'point') {
279
+ let coord = d.geometry.coordinates
280
+ obj.x = _.round(coord[0], 6)
281
+ obj.y = _.round(coord[1], 6)
282
+ }
283
+ delete obj.geometry
284
+ return obj
285
+ })
286
+ }
287
+
288
+ function getAnnotationDetails (annotations, taskInfo) {
289
+ let {data_type} = taskInfo
290
+ let colums = {
291
+ point: getPointColums(data_type),
292
+ line: getLineColums(data_type),
293
+ polygon: getPolygonColums(data_type)
294
+ }
295
+ let sortIndex = {point: 1, line: 2, polygon: 3}
296
+ let sorted = annotations.sort((a, b) => sortIndex[a.type] - sortIndex[b.type])
297
+ let items = []
298
+ let features = []
299
+ sorted.forEach(anno => {
300
+ let {id, type, name, color} = anno
301
+ let data = {}
302
+ colums[type].forEach(col => {
303
+ if (col.key === 'color' || col.key === 'name') {
304
+ return
305
+ }
306
+ if (type === 'point') {
307
+ let coord = anno.geometry.coordinates
308
+ anno.x = _.round(coord[0], 6)
309
+ anno.y = _.round(coord[1], 6)
310
+ }
311
+ data[col.title] = anno[col.key]
312
+ })
313
+ let feature = getAnnotationFeature(anno)
314
+ features.push(feature)
315
+ items.push({
316
+ id,
317
+ name,
318
+ type,
319
+ color,
320
+ data,
321
+ feature,
322
+ image: ''
323
+ })
324
+ })
325
+ return items
326
+ }
327
+
328
+ function getStyle (features, report) {
329
+ let style = _.cloneDeep(mapStyle)
330
+ style.layers.push({
331
+ id: 'background',
332
+ type: 'background',
333
+ paint: {
334
+ 'background-color': '#000000'
335
+ }
336
+ })
337
+ let {taskName, projectName, user_id, data_type} = report
338
+ let type = data_type === 'infrared' ? 'tmp' : 'dom'
339
+ let tileUrl = `http://127.0.0.1:10024/${user_id}/${projectName}/${taskName}/result/2D/${layerFolders[type]}/{z}/{x}/{y}.webp`
340
+ style.sources.dom_image = {
341
+ 'type': 'raster',
342
+ 'tiles': [ tileUrl ],
343
+ 'tileSize': 512
344
+ }
345
+ style.sources.annotation = {
346
+ type: 'geojson',
347
+ data: {
348
+ type: 'FeatureCollection',
349
+ features
350
+ }
351
+ }
352
+ style.layers.push({
353
+ 'id': 'dom_image',
354
+ 'source': 'dom_image',
355
+ 'type': 'raster'
356
+ })
357
+ style.layers.push({
358
+ 'id': 'annotation-point',
359
+ 'type': 'circle',
360
+ filter: ['==', '$type', 'Point'],
361
+ source: 'annotation',
362
+ 'paint': {
363
+ 'circle-radius': 10,
364
+ 'circle-color': ['get', 'color']
365
+ }
366
+ })
367
+ style.layers.push({
368
+ 'id': 'annotation-line',
369
+ 'type': 'line',
370
+ filter: ['!=', '$type', 'Point'],
371
+ source: 'annotation',
372
+ 'layout': {
373
+ 'line-cap': 'round',
374
+ 'line-join': 'round'
375
+ },
376
+ 'paint': {
377
+ 'line-color': ['get', 'color'],
378
+ 'line-width': 4
379
+ }
380
+ })
381
+ style.layers.push({
382
+ 'id': 'annotation-polygon',
383
+ 'type': 'fill',
384
+ filter: ['==', '$type', 'Polygon'],
385
+ source: 'annotation',
386
+ 'paint': {
387
+ 'fill-color': ['get', 'color'],
388
+ 'fill-opacity': 0.1
389
+ }
390
+ })
391
+ return style
392
+ }
393
+
394
+ export function initMap (features, report) {
395
+ return new Promise((resolve) => {
396
+ let style = getStyle(features, report)
397
+ let div = document.createElement('div')
398
+ div.style.width = '800px'
399
+ div.style.height = '400px'
400
+ div.style.position = 'absolute'
401
+ div.style.zIndex = '-1'
402
+ document.body.appendChild(div)
403
+ let map = new mapboxgl.Map({
404
+ container: div,
405
+ style: style,
406
+ bearing: 0,
407
+ pitch: 0,
408
+ center: [110.9, 32.79],
409
+ zoom: 4.2,
410
+ interactive: true,
411
+ doubleClickZoom: false,
412
+ dragRotate: false,
413
+ pitchWithRotate: false,
414
+ preserveDrawingBuffer: true,
415
+ localFontFamily: 'sans-serif'
416
+ })
417
+ map.on('load', () => {
418
+ resolve(map)
419
+ })
420
+ })
421
+
422
+ }
423
+
424
+ export function removeMap (map) {
425
+ map.remove()
426
+ document.body.removeChild(map.getContainer())
427
+ }
428
+
429
+ export function getImage (map, feature) {
430
+ let extent = turf.bbox(feature)
431
+ let {id} = feature.properties
432
+ map.fitBounds(extent, {
433
+ animate: false,
434
+ padding: 50,
435
+ maxZoom: 19
436
+ })
437
+ map.setFilter('annotation-point', ['all', ['==', '$type', 'Point'], ['==', 'id', id]])
438
+ map.setFilter('annotation-line', ['all', ['!=', '$type', 'Point'], ['==', 'id', id]])
439
+ map.setFilter('annotation-polygon', ['all', ['==', '$type', 'Polygon'], ['==', 'id', id]])
440
+ return new Promise((resolve) => {
441
+ map.once('idle', () => {
442
+ let canvas = map.getCanvas()
443
+ resolve(canvas.toDataURL('image/png'))
444
+ })
445
+ })
446
+ }
@@ -0,0 +1,2 @@
1
+ export {default as generateReconstructReport} from './report'
2
+ export * as annotationReport from './annotations_report'
@@ -0,0 +1,81 @@
1
+ import * as turf from '@turf/turf'
2
+ import _ from 'lodash'
3
+ import { transformCoordinateToLngLat } from '../transform'
4
+
5
+ const EARTH_RADIUS = 6378137
6
+
7
+ export function getFeatures (photoList, imagePosDiff, zoom) {
8
+ let features = []
9
+ let minDiff_z = Infinity
10
+ let maxDiff_z = -Infinity
11
+ let ratio = getRatio(zoom, imagePosDiff)
12
+ let endPoints = []
13
+ // 先获取坐标系
14
+ let item = photoList.find(d => d.meta_data.pos)
15
+ if (item) {
16
+ let coordinate_system = _.cloneDeep(item.meta_data.coordinate_system)
17
+ photoList.forEach((photo, index) => {
18
+ let {meta_data} = photo
19
+ let startPoint = transformCoordinateToLngLat(meta_data.pos, coordinate_system)
20
+ if (!startPoint) {
21
+ return
22
+ }
23
+ let id = index + 1
24
+ let diffItem = imagePosDiff.find(d => d.id === id)
25
+ if (!diffItem) {
26
+ return
27
+ }
28
+ // 计算z方向的误差最大值,用于给椭圆设置颜色
29
+ let diffs = diffItem.pos_diff
30
+ minDiff_z = Math.min(minDiff_z, diffs[2])
31
+ maxDiff_z = Math.max(maxDiff_z, diffs[2])
32
+ let endPoint = _.cloneDeep(startPoint)
33
+ endPoint[0] += diffs[0] * ratio * getDegreePerMeters()
34
+ endPoint[1] += diffs[1] * ratio * getDegreePerMeters()
35
+ endPoints.push(endPoint)
36
+ features.push(turf.lineString([startPoint, endPoint], {
37
+ diff_x: 100 * diffs[0],
38
+ diff_y: 100 * diffs[1],
39
+ diff_z: 100 * diffs[2]
40
+ }))
41
+ })
42
+ }
43
+ return {
44
+ geojson: turf.featureCollection(features),
45
+ ratio,
46
+ minDiff_z,
47
+ maxDiff_z,
48
+ endPoints
49
+ }
50
+ }
51
+
52
+ // 获取误差椭圆的缩放系数。要把误差直观反应到地图上需要对误差椭圆进行缩放,其缩放的大小跟误差的大小和当前地图的级别有关
53
+ // 本函数假设要让最大的误差在当前地图层级上显示出30像素的大小,以此来预估缩放系数
54
+ function getRatio (zoom, diffs) {
55
+ // 最大误差值
56
+ let max = diffs.reduce((m, item) => {
57
+ if (!item.pos_diff?.length) {
58
+ return m
59
+ }
60
+ let d = Math.max(Math.abs(item.pos_diff[0]), Math.abs(item.pos_diff[1]))
61
+ return m > d ? m : d
62
+ })
63
+ // 地图上30像素代表的距离
64
+ let distance = 30 * 2 * Math.PI * EARTH_RADIUS / (Math.pow(2, zoom) * 512)
65
+ let ratio = distance / max
66
+ if (ratio < 1) {
67
+ return Number(ratio.toFixed(1))
68
+ }
69
+ if (ratio < 10) {
70
+ return Math.round(ratio)
71
+ }
72
+ if (ratio < 100) {
73
+ return 10 * parseInt(ratio / 10)
74
+ }
75
+ return Math.max(Math.round(ratio / 100) * 100, 100)
76
+ }
77
+
78
+ // 墨卡托坐标系每米对应的经纬度度数
79
+ function getDegreePerMeters () {
80
+ return 180 / (Math.PI * EARTH_RADIUS)
81
+ }