@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.
- package/dist/catchUtil.min.esm.js +67984 -14011
- package/dist/catchUtil.min.js +2695 -55
- package/package.json +22 -3
- package/src/constants/annotation_color.js +7 -0
- package/src/constants/annotation_draw_style.js +228 -0
- package/src/constants/annotation_label_style.js +76 -0
- package/src/constants/annotation_style.js +118 -0
- package/src/constants/cameras.js +1 -1
- package/src/constants/crs.js +31473 -31473
- package/src/constants/error_codes.js +44 -0
- package/src/constants/height_colors.js +1 -0
- package/src/constants/index.js +9 -2
- package/src/constants/map_style.js +11 -0
- package/src/constants/measurement_fields.js +3 -3
- package/src/{event.js → event/event.js} +1 -14
- package/src/event/event_bus.js +5 -0
- package/src/event/index.js +2 -0
- package/src/gl-operations/constants.js +9 -11
- package/src/gl-operations/default_options.js +5 -5
- package/src/gl-operations/index.js +166 -239
- package/src/gl-operations/reglCommands/contours.js +20 -20
- package/src/gl-operations/reglCommands/default.js +34 -34
- package/src/gl-operations/reglCommands/hillshading.js +116 -116
- package/src/gl-operations/reglCommands/index.js +6 -6
- package/src/gl-operations/reglCommands/multiLayers.js +55 -55
- package/src/gl-operations/reglCommands/transitions.js +24 -24
- package/src/gl-operations/reglCommands/util.js +54 -54
- package/src/gl-operations/renderer.js +69 -69
- package/src/gl-operations/shaders/transform.js +2 -2
- package/src/gl-operations/shaders/util/rgbaToFloat.glsl +11 -11
- package/src/gl-operations/texture_manager.js +58 -58
- package/src/gl-operations/util.js +154 -154
- package/src/index.js +14 -2
- package/src/measure/index.js +198 -0
- package/src/measure/tile_cache.js +88 -0
- package/src/mvs/index.js +26 -0
- package/src/mvs/protos/index.js +12 -0
- package/src/mvs/protos/proto_10.js +155 -0
- package/src/observation_pretict.js +168 -0
- package/src/photo-parser/exif/gps_tags.js +33 -0
- package/src/photo-parser/exif/ifd1_tags.js +22 -0
- package/src/photo-parser/exif/index.js +130 -0
- package/src/photo-parser/exif/parse_image.js +290 -0
- package/src/photo-parser/exif/string_values.js +137 -0
- package/src/photo-parser/exif/tags.js +75 -0
- package/src/photo-parser/exif/tiff_tags.js +35 -0
- package/src/photo-parser/exif/util.js +103 -0
- package/src/photo-parser/image-size/detector.js +24 -0
- package/src/photo-parser/image-size/fromFile.js +55 -0
- package/src/photo-parser/image-size/index.js +2 -0
- package/src/photo-parser/image-size/lookup.js +37 -0
- package/src/photo-parser/image-size/types/bmp.js +10 -0
- package/src/photo-parser/image-size/types/cur.js +16 -0
- package/src/photo-parser/image-size/types/dds.js +10 -0
- package/src/photo-parser/image-size/types/gif.js +11 -0
- package/src/photo-parser/image-size/types/heif.js +35 -0
- package/src/photo-parser/image-size/types/icns.js +112 -0
- package/src/photo-parser/image-size/types/ico.js +74 -0
- package/src/photo-parser/image-size/types/index.js +43 -0
- package/src/photo-parser/image-size/types/j2c.js +11 -0
- package/src/photo-parser/image-size/types/jp2.js +22 -0
- package/src/photo-parser/image-size/types/jpg.js +157 -0
- package/src/photo-parser/image-size/types/ktx.js +18 -0
- package/src/photo-parser/image-size/types/png.js +36 -0
- package/src/photo-parser/image-size/types/pnm.js +74 -0
- package/src/photo-parser/image-size/types/psd.js +10 -0
- package/src/photo-parser/image-size/types/svg.js +100 -0
- package/src/photo-parser/image-size/types/tga.js +14 -0
- package/src/photo-parser/image-size/types/tiff.js +92 -0
- package/src/photo-parser/image-size/types/utils.js +83 -0
- package/src/photo-parser/image-size/types/webp.js +67 -0
- package/src/photo-parser/index.js +181 -0
- package/src/report/annotations_report.js +446 -0
- package/src/report/index.js +2 -0
- package/src/report/map_util.js +81 -0
- package/src/report/pdf_creator.js +247 -0
- package/src/report/report.js +583 -0
- package/src/transform.js +204 -0
- package/src/util.js +371 -75
- package/CHANGELOG.md +0 -60
- /package/src/constants/{colors.js → dsm_colors.js} +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import cdt2d from 'cdt2d'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
import TileCache from './tile_cache'
|
|
4
|
+
import { getTileCoord, getTilePixelCoord, geographicToCartesian } from '../transform'
|
|
5
|
+
import { rgbaToFloat } from '../util'
|
|
6
|
+
|
|
7
|
+
class Measurement {
|
|
8
|
+
constructor (options) {
|
|
9
|
+
if (!options.loadTileImage) {
|
|
10
|
+
throw new Error('未指定加载瓦片数据的方法')
|
|
11
|
+
}
|
|
12
|
+
this.tileCache = new TileCache({
|
|
13
|
+
loadTileImage: options.loadTileImage,
|
|
14
|
+
maxCacheSize: options.maxCacheSize || 200
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// 从dsm中获取指定位置的高程
|
|
20
|
+
async getFloatValue (lngLat, options) {
|
|
21
|
+
if (lngLat[2]) {
|
|
22
|
+
// 如果传入的坐标本身带有高程,则不用去dsm中获取高程
|
|
23
|
+
return _.round(lngLat[2], 4)
|
|
24
|
+
}
|
|
25
|
+
let {tilejson} = options
|
|
26
|
+
if (!tilejson) {
|
|
27
|
+
// 没有dsm
|
|
28
|
+
return 0
|
|
29
|
+
}
|
|
30
|
+
let { max_zoom } = tilejson
|
|
31
|
+
let { min, max } = tilejson.metadata
|
|
32
|
+
let tileCoord = getTileCoord(max_zoom, lngLat)
|
|
33
|
+
let pixelCoord = getTilePixelCoord(max_zoom, lngLat)
|
|
34
|
+
let tile = this.tileCache.getTile(tileCoord, options)
|
|
35
|
+
if (!tile) {
|
|
36
|
+
tile = this.tileCache.addTile(tileCoord, options)
|
|
37
|
+
}
|
|
38
|
+
if (!tile.loaded) {
|
|
39
|
+
await tile.tileData
|
|
40
|
+
}
|
|
41
|
+
if (!tile.tileData) {
|
|
42
|
+
// 没找到瓦片,说明该点超出瓦片范围,默认给一个平均值
|
|
43
|
+
return _.round((min + max) / 2, 4)
|
|
44
|
+
}
|
|
45
|
+
let pixelIndex = (pixelCoord.y * 512 + pixelCoord.x) * 4
|
|
46
|
+
let value = rgbaToFloat([
|
|
47
|
+
tile.tileData[pixelIndex],
|
|
48
|
+
tile.tileData[pixelIndex + 1],
|
|
49
|
+
tile.tileData[pixelIndex + 2],
|
|
50
|
+
tile.tileData[pixelIndex + 3]
|
|
51
|
+
])
|
|
52
|
+
if (Math.abs(value + 99999) <= Math.max(Math.abs(value), Math.abs(-99999)) * 0.0001) {
|
|
53
|
+
// 无效值
|
|
54
|
+
return _.round((min + max) / 2, 4)
|
|
55
|
+
}
|
|
56
|
+
return _.round(value, 4)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getMeasureInfo (options) {
|
|
60
|
+
let { data_type, geometry } = options
|
|
61
|
+
let type = geometry.type
|
|
62
|
+
let properties = {}
|
|
63
|
+
if (type === 'Point') {
|
|
64
|
+
// 点要素
|
|
65
|
+
let value = await this.getFloatValue(geometry.coordinates, options)
|
|
66
|
+
if (data_type === 'infrared') {
|
|
67
|
+
// 红外
|
|
68
|
+
properties.temprature = _.round(value, 4)
|
|
69
|
+
properties.lng = geometry.coordinates[0]
|
|
70
|
+
properties.lat = geometry.coordinates[1]
|
|
71
|
+
} else {
|
|
72
|
+
geometry.coordinates[2] = value
|
|
73
|
+
properties.height = value
|
|
74
|
+
properties.lng = geometry.coordinates[0]
|
|
75
|
+
properties.lat = geometry.coordinates[1]
|
|
76
|
+
}
|
|
77
|
+
} else if (type === 'LineString') {
|
|
78
|
+
let coordinates = geometry.coordinates
|
|
79
|
+
let slopes = []
|
|
80
|
+
let values = await Promise.all(coordinates.map(coord => this.getFloatValue(coord, options)))
|
|
81
|
+
if (data_type === 'infrared') {
|
|
82
|
+
// 红外
|
|
83
|
+
properties.distance2d = this.getDistance2D(coordinates)
|
|
84
|
+
properties.temprature_min = _.round(_.min(values), 4)
|
|
85
|
+
properties.temprature_max = _.round(_.max(values), 4)
|
|
86
|
+
} else {
|
|
87
|
+
coordinates.forEach((coord, index) => {
|
|
88
|
+
coord[2] = values[index]
|
|
89
|
+
if (index > 0) {
|
|
90
|
+
slopes.push(this.getSlope(coordinates[index - 1], coordinates[index]))
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
properties.distance2d = this.getDistance2D(coordinates)
|
|
94
|
+
properties.distance3d = this.getDistance3D(coordinates)
|
|
95
|
+
properties.height_min = _.min(values)
|
|
96
|
+
properties.height_max = _.max(values)
|
|
97
|
+
properties.slope_min = _.min(slopes)
|
|
98
|
+
properties.slope_max = _.max(slopes)
|
|
99
|
+
}
|
|
100
|
+
} else if (type === 'Polygon') {
|
|
101
|
+
let coordinates = geometry.coordinates[0]
|
|
102
|
+
let values = await Promise.all(coordinates.map(coord => this.getFloatValue(coord, options)))
|
|
103
|
+
if (data_type === 'infrared') {
|
|
104
|
+
// 红外
|
|
105
|
+
properties.distance2d = this.getDistance2D(coordinates)
|
|
106
|
+
properties.area2d = this.getArea(coordinates).area2d
|
|
107
|
+
properties.temprature_min = _.round(_.min(values), 4)
|
|
108
|
+
properties.temprature_max = _.round(_.max(values), 4)
|
|
109
|
+
} else {
|
|
110
|
+
coordinates.forEach((coord, index) => {
|
|
111
|
+
coord[2] = values[index]
|
|
112
|
+
})
|
|
113
|
+
properties.distance2d = this.getDistance2D(coordinates)
|
|
114
|
+
properties.distance3d = this.getDistance3D(coordinates)
|
|
115
|
+
properties.height_min = _.min(values)
|
|
116
|
+
properties.height_max = _.max(values)
|
|
117
|
+
let area = this.getArea(coordinates)
|
|
118
|
+
properties.area2d = area.area2d
|
|
119
|
+
properties.area3d = area.area3d
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { geometry, properties }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getSlope (from, to) {
|
|
126
|
+
let d = this.getDistance3D([from, to])
|
|
127
|
+
let hDiff = Math.abs(from[2] - to[2]) // 高差
|
|
128
|
+
return _.round((Math.asin(hDiff / d) * 180) / Math.PI, 4)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getDistance2D (coordinates) {
|
|
132
|
+
let d = 0
|
|
133
|
+
for (let i = 1; i < coordinates.length; i++) {
|
|
134
|
+
let p1 = geographicToCartesian([coordinates[i - 1][0], coordinates[i - 1][1], 0])
|
|
135
|
+
let p2 = geographicToCartesian([coordinates[i][0], coordinates[i][1], 0])
|
|
136
|
+
d += Math.sqrt(
|
|
137
|
+
Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2) + Math.pow(p1[2] - p2[2], 2)
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
return _.round(d, 4)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getDistance3D (coordinates) {
|
|
144
|
+
let d = 0
|
|
145
|
+
for (let i = 1; i < coordinates.length; i++) {
|
|
146
|
+
let p1 = geographicToCartesian(coordinates[i - 1])
|
|
147
|
+
let p2 = geographicToCartesian(coordinates[i])
|
|
148
|
+
d += Math.sqrt(
|
|
149
|
+
Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2) + Math.pow(p1[2] - p2[2], 2)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
return _.round(d, 4)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
getArea (coordinates) {
|
|
156
|
+
let triangles = []
|
|
157
|
+
let flatCoordinates
|
|
158
|
+
let projs = [coordinates.map(d => [d[0], d[1]]), coordinates.map(d => [d[0], d[2]]), coordinates.map(d => [d[1], d[2]])] // 三个投影平面
|
|
159
|
+
let flatCoordinates2d = projs[0] // 2D地图的投影平面
|
|
160
|
+
|
|
161
|
+
while (!triangles.length && projs.length) { // 寻找可成功分解的投影平面
|
|
162
|
+
flatCoordinates = projs.shift()
|
|
163
|
+
flatCoordinates.pop() // 去掉最后一个重复的点
|
|
164
|
+
let edges = []
|
|
165
|
+
flatCoordinates.forEach((coord, index) => {
|
|
166
|
+
if (index === flatCoordinates.length - 1) {
|
|
167
|
+
edges.push([index, 0])
|
|
168
|
+
} else {
|
|
169
|
+
edges.push([index, index + 1])
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
triangles = cdt2d(flatCoordinates, edges, { exterior: false })
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let area2d = 0,
|
|
176
|
+
area3d = 0
|
|
177
|
+
triangles.forEach(triangle => {
|
|
178
|
+
let [i1, i2, i3] = triangle
|
|
179
|
+
area2d += this.getTriangleArea(
|
|
180
|
+
[flatCoordinates2d[i1][0], flatCoordinates2d[i1][1], 0],
|
|
181
|
+
[flatCoordinates2d[i2][0], flatCoordinates2d[i2][1], 0],
|
|
182
|
+
[flatCoordinates2d[i3][0], flatCoordinates2d[i3][1], 0]
|
|
183
|
+
)
|
|
184
|
+
area3d += this.getTriangleArea(coordinates[i1], coordinates[i2], coordinates[i3])
|
|
185
|
+
})
|
|
186
|
+
return { area2d: _.round(area2d, 4), area3d: _.round(area3d, 4) }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 海伦公式:空间中三角形三个顶点坐标(经纬度),计算三角形面积
|
|
190
|
+
getTriangleArea (p1, p2, p3) {
|
|
191
|
+
let a = this.getDistance3D([p1, p2])
|
|
192
|
+
let b = this.getDistance3D([p2, p3])
|
|
193
|
+
let c = this.getDistance3D([p3, p1])
|
|
194
|
+
return Math.sqrt((a + b + c) * (a + b - c) * (a + c - b) * (b + c - a)) / 4
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export default Measurement
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { decode, toRGBA8 } from 'upng-js'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
|
|
4
|
+
class TileCache {
|
|
5
|
+
constructor (options) {
|
|
6
|
+
this.tiles = []
|
|
7
|
+
this._loadTileImage = options.loadTileImage
|
|
8
|
+
this.MAX_SIZE = options.maxCacheSize
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
addTile (tileCoord, options) {
|
|
12
|
+
let { user_id, project_id, task_id, data_type } = options
|
|
13
|
+
let tile = {
|
|
14
|
+
loaded: false,
|
|
15
|
+
user_id,
|
|
16
|
+
project_id,
|
|
17
|
+
task_id,
|
|
18
|
+
data_type,
|
|
19
|
+
tileCoord
|
|
20
|
+
}
|
|
21
|
+
tile.tileData = this._loadTileData(tile, options)
|
|
22
|
+
this.tiles.push(tile)
|
|
23
|
+
return tile
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async _loadTileData (tile) {
|
|
27
|
+
let { user_id, project_id, task_id, data_type, tileCoord } = tile
|
|
28
|
+
let originalTile = null
|
|
29
|
+
try {
|
|
30
|
+
originalTile = await this._loadTileImage(user_id, project_id, task_id, data_type, tileCoord)
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error(err)
|
|
33
|
+
originalTile = null
|
|
34
|
+
}
|
|
35
|
+
if (!originalTile) {
|
|
36
|
+
tile.loaded = true
|
|
37
|
+
tile.lastVisited = new Date()
|
|
38
|
+
tile.tileData = null
|
|
39
|
+
return tile
|
|
40
|
+
}
|
|
41
|
+
const img = decode(originalTile)
|
|
42
|
+
const rgba = new Uint8Array(toRGBA8(img)[0])
|
|
43
|
+
tile.loaded = true
|
|
44
|
+
tile.lastVisited = new Date()
|
|
45
|
+
tile.tileData = rgba
|
|
46
|
+
|
|
47
|
+
if (this.tiles.length > this.MAX_SIZE) {
|
|
48
|
+
this.removeTile()
|
|
49
|
+
}
|
|
50
|
+
return tile
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getTile (tileCoord, options) {
|
|
54
|
+
let { user_id, task_id, data_type } = options
|
|
55
|
+
for (let tile of this.tiles) {
|
|
56
|
+
if (
|
|
57
|
+
tile.user_id === user_id &&
|
|
58
|
+
tile.task_id === task_id &&
|
|
59
|
+
tile.data_type === data_type &&
|
|
60
|
+
_.isEqual(tile.tileCoord, tileCoord)
|
|
61
|
+
) {
|
|
62
|
+
tile.lastVisited = new Date()
|
|
63
|
+
return tile
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
removeTile () {
|
|
70
|
+
// 删除离上次访问时间最长的那个瓦片
|
|
71
|
+
let tileIndex = -1,
|
|
72
|
+
tile = null
|
|
73
|
+
for (let i = 0; i < this.tiles.length; i++) {
|
|
74
|
+
if (tile && tile.lastVisited > this.tiles[i].lastVisited) {
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
tileIndex = i
|
|
78
|
+
tile = this.tiles[i]
|
|
79
|
+
}
|
|
80
|
+
this.tiles.splice(tileIndex, 1)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
clear () {
|
|
84
|
+
this.tiles = []
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default TileCache
|
package/src/mvs/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Pbf from 'pbf'
|
|
2
|
+
import getProtoStr from './protos'
|
|
3
|
+
import {compile} from 'pbf/compile'
|
|
4
|
+
import schema from 'protocol-buffers-schema'
|
|
5
|
+
|
|
6
|
+
function _getProtoVersion (taskInfo) {
|
|
7
|
+
let version = '1.0'
|
|
8
|
+
if (taskInfo.metadata?.environment) {
|
|
9
|
+
return taskInfo.metadata.environment.protoVersion
|
|
10
|
+
}
|
|
11
|
+
return version
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseMVS (buffer, taskInfo) {
|
|
15
|
+
if (!taskInfo.task_id || !buffer) {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
let protoVersion = _getProtoVersion(taskInfo)
|
|
19
|
+
let protoStr = getProtoStr(protoVersion)
|
|
20
|
+
if (!protoStr) {
|
|
21
|
+
throw new Error('不支持的格式')
|
|
22
|
+
}
|
|
23
|
+
let proto = schema.parse(protoStr)
|
|
24
|
+
let {MVSBlock} = compile(proto)
|
|
25
|
+
return MVSBlock.read(new Pbf(buffer))
|
|
26
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
export default `
|
|
2
|
+
syntax = "proto2";
|
|
3
|
+
package mipmap.engine.message;
|
|
4
|
+
|
|
5
|
+
message Point3d {
|
|
6
|
+
required double x = 1;
|
|
7
|
+
required double y = 2;
|
|
8
|
+
required double z = 3;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
message Point2f {
|
|
12
|
+
required float x = 1;
|
|
13
|
+
required float y = 2;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
message Point3f {
|
|
17
|
+
required float x = 1;
|
|
18
|
+
required float y = 2;
|
|
19
|
+
required float z = 3;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
message ImageRect {
|
|
23
|
+
required int32 x = 1;
|
|
24
|
+
required int32 y = 2;
|
|
25
|
+
required int32 width = 3;
|
|
26
|
+
required int32 height = 4;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
message BoundingBox3d {
|
|
30
|
+
repeated Point3d corner = 1; //长度为8,按照Eigen::BoundingBox3d排列
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
message CoordinateSystem {
|
|
34
|
+
enum CoordinateSystemType {
|
|
35
|
+
LOCAL_ENU = 0;
|
|
36
|
+
LOCAL = 1;
|
|
37
|
+
Geographic = 2;
|
|
38
|
+
Projected = 3;
|
|
39
|
+
Geocentric = 4;
|
|
40
|
+
Unsupported = 5;
|
|
41
|
+
};
|
|
42
|
+
required CoordinateSystemType type = 1;
|
|
43
|
+
optional int32 epsg_code = 2;
|
|
44
|
+
optional string wkt = 3;
|
|
45
|
+
repeated double offset = 4;
|
|
46
|
+
repeated double origin_point = 5;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
message ImageMetaData {
|
|
50
|
+
optional Point3d position = 2;
|
|
51
|
+
optional CoordinateSystem position_cs = 201;
|
|
52
|
+
optional Point3d position_sigma = 3;
|
|
53
|
+
optional Point3d pitch_roll_yaw = 4;
|
|
54
|
+
repeated double rotation_c2w = 5; //size = 9
|
|
55
|
+
optional double relative_altitude = 6;
|
|
56
|
+
optional double absolute_altitude = 7;
|
|
57
|
+
|
|
58
|
+
enum BandType {
|
|
59
|
+
RGB = 1;
|
|
60
|
+
Red = 2;
|
|
61
|
+
Green= 3;
|
|
62
|
+
Blue = 4;
|
|
63
|
+
NIR = 5;
|
|
64
|
+
RedEdge = 6;
|
|
65
|
+
};
|
|
66
|
+
optional BandType band_type = 8;
|
|
67
|
+
optional string capture_uuid = 9;
|
|
68
|
+
optional string drone_id = 10;
|
|
69
|
+
// 多光谱影像相关参数
|
|
70
|
+
optional double black_current = 11;
|
|
71
|
+
repeated double vignetting_data = 12; //size = 6
|
|
72
|
+
optional double sensor_gain = 13;
|
|
73
|
+
optional double sensor_gain_adjustment = 14;
|
|
74
|
+
optional double exposure_time = 15;
|
|
75
|
+
optional double irradiance = 16;
|
|
76
|
+
|
|
77
|
+
optional string camera_model = 17;
|
|
78
|
+
required int32 width = 18;
|
|
79
|
+
required int32 height = 19;
|
|
80
|
+
optional bool dewrap_flag = 20;
|
|
81
|
+
optional int32 rtk_flag = 21;
|
|
82
|
+
optional uint64 capture_time = 22;
|
|
83
|
+
optional uint32 focal_length_in_35mm = 23;
|
|
84
|
+
optional uint32 camera_id = 24;
|
|
85
|
+
repeated double pre_calib_parameters = 25;
|
|
86
|
+
optional bool position_constant = 26;
|
|
87
|
+
optional double f_number = 27;
|
|
88
|
+
optional double ios_speed_rating = 28;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
message ControlPoint {
|
|
92
|
+
required string ctrl_id = 1;
|
|
93
|
+
required uint32 usage = 2; // 0 for control point, 1 for check point, 2 for no usage
|
|
94
|
+
required Point3d point = 3;
|
|
95
|
+
required Point3d sigma = 4;
|
|
96
|
+
repeated uint32 img_id = 5;
|
|
97
|
+
repeated Point2f observation = 6;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
message ControlPointGroup {
|
|
101
|
+
repeated ControlPoint points = 1;
|
|
102
|
+
required CoordinateSystem coordiante_system = 2;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
message MVSCamera {
|
|
106
|
+
enum DistortionType {
|
|
107
|
+
OPENCV_K1K2K3P1P2 = 1;
|
|
108
|
+
UnKnow = 2;
|
|
109
|
+
};
|
|
110
|
+
required DistortionType distortion_type = 1;
|
|
111
|
+
optional int32 camera_id = 2;
|
|
112
|
+
repeated double camera_params = 3;
|
|
113
|
+
required int32 width = 4;
|
|
114
|
+
required int32 height = 5;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
message MVSImage {
|
|
118
|
+
required uint32 img_id = 1;
|
|
119
|
+
optional string img_path = 2;
|
|
120
|
+
required uint32 camera_id = 3;
|
|
121
|
+
repeated double projection_matrix = 4;
|
|
122
|
+
required ImageRect image_rect = 5;
|
|
123
|
+
repeated double color_params = 6;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
message MVSPoint {
|
|
127
|
+
required Point3d point = 1;
|
|
128
|
+
optional int32 rgb = 2; // (r << 16) | (g << 8) | b
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
message MVSObservation {
|
|
132
|
+
required float x = 1;
|
|
133
|
+
required float y = 2;
|
|
134
|
+
required int32 img_index = 3;
|
|
135
|
+
required int32 pnt_index = 4;
|
|
136
|
+
optional int32 rgb = 5; // (r << 16) | (g << 8) | b
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
message ImageMetaDataWithID {
|
|
140
|
+
required uint32 id=1;
|
|
141
|
+
required ImageMetaData meta_data=2;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
message MVSBlock {
|
|
145
|
+
repeated MVSCamera camera = 1;
|
|
146
|
+
repeated MVSImage image = 2;
|
|
147
|
+
repeated MVSPoint point = 3;
|
|
148
|
+
required CoordinateSystem mvs_image_coordinate_system = 5;
|
|
149
|
+
required BoundingBox3d bounding_box = 6;
|
|
150
|
+
required BoundingBox3d tight_bounding_box = 7;
|
|
151
|
+
optional CoordinateSystem meta_data_coordinate_system = 9;
|
|
152
|
+
optional ControlPointGroup control_point_group = 10;
|
|
153
|
+
optional string version = 11;
|
|
154
|
+
}
|
|
155
|
+
`
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {mat3, vec3, vec2} from 'gl-matrix'
|
|
2
|
+
import satellite_geo_calc from 'satellite-geo-calc'
|
|
3
|
+
import { transformCoordinateToLngLat } from './transform'
|
|
4
|
+
import * as math from 'mathjs'
|
|
5
|
+
|
|
6
|
+
// 刺点预测
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {*} cameraInfo.images 照片列表,包含{img_id, img_path, camera_id, image_rect, projection_matrix}
|
|
10
|
+
* @param {*} cameraInfo.cameras 相机列表,包含{camera_id, camera_params, width, height}
|
|
11
|
+
* @param {*} controlPoint 当前控制点,包含{id, coordinate, usage, observations}
|
|
12
|
+
*/
|
|
13
|
+
export function predictObservations (cameraInfo, controlPoint, control_coordinate_system) {
|
|
14
|
+
let {observations} = controlPoint
|
|
15
|
+
if (observations.length > 1) {
|
|
16
|
+
return predictOptimize(cameraInfo, controlPoint)
|
|
17
|
+
} else {
|
|
18
|
+
return predictBeforeAT(cameraInfo, controlPoint, control_coordinate_system)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function predictBeforeAT (cameraInfo, controlPoint, control_coordinate_system) {
|
|
23
|
+
let {images, cameras, coordinateSystem} = cameraInfo
|
|
24
|
+
let camersMap = {}
|
|
25
|
+
cameras.forEach(c => camersMap[c.camera_id] = c)
|
|
26
|
+
let {coordinate} = controlPoint
|
|
27
|
+
let Xw = vec3.fromValues(0, 0, 0)
|
|
28
|
+
if (control_coordinate_system.type === 1) {
|
|
29
|
+
Xw = vec3.fromValues(coordinate[1], coordinate[1], coordinate[2])
|
|
30
|
+
} else {
|
|
31
|
+
let lngLat = transformCoordinateToLngLat(coordinate, control_coordinate_system)
|
|
32
|
+
let originalPoint = coordinateSystem.origin_point // enu的坐标原点
|
|
33
|
+
let Xenu = satellite_geo_calc.coordinateTransfer.lla_to_enu({ lon:lngLat[0], lat:lngLat[1], alt: lngLat[2] || 0}, {lon: originalPoint[0], lat: originalPoint[1], alt: originalPoint[2]})
|
|
34
|
+
Xw = vec3.fromValues(Xenu.x, Xenu.y, Xenu.z)
|
|
35
|
+
}
|
|
36
|
+
let predicts = {}
|
|
37
|
+
images.forEach(image => {
|
|
38
|
+
let {img_id, camera_id, projection_matrix, image_rect} = image
|
|
39
|
+
let parameters = camersMap[camera_id].camera_params
|
|
40
|
+
let {width, height} = image_rect
|
|
41
|
+
let P = projection_matrix
|
|
42
|
+
let R = mat3.fromValues(P[0], P[4], P[8], P[1], P[5], P[9], P[2], P[6], P[10]) // 旋转矩阵, 列主序
|
|
43
|
+
let T = vec3.fromValues(P[3], P[7], P[11]) // 平移向量
|
|
44
|
+
let K = mat3.fromValues(parameters[0], 0, 0, 0, parameters[1], 0, parameters[2], parameters[3], 1)
|
|
45
|
+
let ph = vec3.transformMat3(vec3.create(), vec3.add(vec3.create(), vec3.transformMat3(vec3.create(), Xw, R), T), K)
|
|
46
|
+
let pixelCoord = vec3.scale(vec3.create, ph, 1 / ph[2])
|
|
47
|
+
if (pixelCoord[0] > 0 && pixelCoord[0] < width && pixelCoord[1] > 0 && pixelCoord[1] < height) {
|
|
48
|
+
// 加上相机畸变
|
|
49
|
+
let p_vec = vec3.fromValues(pixelCoord[0], pixelCoord[1], 1)
|
|
50
|
+
let x_n = vec3.transformMat3(vec3.create(), p_vec, mat3.invert(mat3.create(), K))
|
|
51
|
+
let dist = distortPoint(x_n, parameters)
|
|
52
|
+
pixelCoord = vec3.transformMat3(vec3.create(), vec3.fromValues(...dist, 1), K)
|
|
53
|
+
predicts[img_id] = [pixelCoord[0], pixelCoord[1]]
|
|
54
|
+
image.predict = {
|
|
55
|
+
uv: [pixelCoord[0], pixelCoord[1]],
|
|
56
|
+
position: {left: pixelCoord[0] * 100 / width, top: pixelCoord[1] * 100 / height},
|
|
57
|
+
distance: Math.hypot(pixelCoord[0] - width / 2, pixelCoord[1] - height / 2)
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
image.predict = undefined
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
return predicts
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function predictOptimize (cameraInfo, controlPoint) {
|
|
67
|
+
let {images, cameras} = cameraInfo
|
|
68
|
+
let {observations} = controlPoint
|
|
69
|
+
let camersMap = {}
|
|
70
|
+
cameras.forEach(c => camersMap[c.camera_id] = c)
|
|
71
|
+
let predicts = {}, observedImages = [] // 已经刺点的图像
|
|
72
|
+
let obsevedMap = new Map()
|
|
73
|
+
observations.forEach((d) => obsevedMap.set(d.id, d.uv))
|
|
74
|
+
images.forEach(image => { // 第一次循环,找出已经完成刺点的图像,后面需要用这些刺点坐标列方程
|
|
75
|
+
if (!obsevedMap.has(image.img_id)) {
|
|
76
|
+
image.observation = undefined
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
image.observation = {
|
|
80
|
+
id: image.img_id,
|
|
81
|
+
uv: obsevedMap.get(image.img_id)
|
|
82
|
+
}
|
|
83
|
+
observedImages.push(image)
|
|
84
|
+
})
|
|
85
|
+
// 根据已经刺的点计算控制点的空间坐标
|
|
86
|
+
let a = [], b = [] // A矩阵和B矩阵的数组,满足Ax + B = 0,其中x未待求的坐标值,行优先
|
|
87
|
+
observedImages.forEach(observedImage => {
|
|
88
|
+
let P_m = observedImage.projection_matrix
|
|
89
|
+
let parameters_m = camersMap[observedImage.camera_id].camera_params
|
|
90
|
+
let undistort_uv = undistortPoint(observedImage.observation.uv, parameters_m)
|
|
91
|
+
let R_m = mat3.fromValues(P_m[0], P_m[4], P_m[8], P_m[1], P_m[5], P_m[9], P_m[2], P_m[6], P_m[10]) // 旋转矩阵, 列主序
|
|
92
|
+
let t_m = vec3.fromValues(P_m[3], P_m[7], P_m[11]) // 平移向量
|
|
93
|
+
let K_m = mat3.fromValues(parameters_m[0], 0, 0, 0, parameters_m[1], 0, parameters_m[2], parameters_m[3], 1)
|
|
94
|
+
let K_m_invert = mat3.invert(mat3.create(), K_m) // K_m的逆矩阵
|
|
95
|
+
// 满足rX = l
|
|
96
|
+
let undistort_uv_norm = vec3.transformMat3(vec3.create(), undistort_uv, K_m_invert)
|
|
97
|
+
a.push([R_m[0] - R_m[2] * undistort_uv_norm[0], R_m[3] - R_m[5] * undistort_uv_norm[0], R_m[6] - R_m[8] * undistort_uv_norm[0]])
|
|
98
|
+
a.push([R_m[1] - R_m[2] * undistort_uv_norm[1], R_m[4] - R_m[5] * undistort_uv_norm[1], R_m[7] - R_m[8] * undistort_uv_norm[1]])
|
|
99
|
+
b.push(t_m[2] * undistort_uv_norm[0] - t_m[0], t_m[2] * undistort_uv_norm[1] - t_m[1])
|
|
100
|
+
})
|
|
101
|
+
let A = math.matrix(a)
|
|
102
|
+
let B = math.matrix(b)
|
|
103
|
+
let A_t = math.transpose(A)
|
|
104
|
+
let result = math.multiply(math.multiply(math.inv(math.multiply(A_t, A)), A_t), B)
|
|
105
|
+
let X = vec3.fromValues(result.get([0]), result.get([1]), result.get([2]))
|
|
106
|
+
|
|
107
|
+
images.forEach(image => { // 第二次循环,计算未刺点图像上的预测坐标
|
|
108
|
+
let {img_id, camera_id, projection_matrix, image_rect} = image
|
|
109
|
+
let parameters = camersMap[camera_id].camera_params
|
|
110
|
+
let {width, height} = image_rect
|
|
111
|
+
if (image.observation) { // 已经刺点,不需要预测
|
|
112
|
+
return image.predict = {
|
|
113
|
+
uv: image.observation.uv,
|
|
114
|
+
position: {left: image.observation.uv[0] * 100 / width, top: image.observation.uv[1] * 100 / height},
|
|
115
|
+
distance: Math.hypot(image.observation.uv[0] - width / 2, image.observation.uv[1] - height / 2)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
let P_n = projection_matrix
|
|
119
|
+
let R_n = mat3.fromValues(P_n[0], P_n[4], P_n[8], P_n[1], P_n[5], P_n[9], P_n[2], P_n[6], P_n[10]) // 旋转矩阵, 列主序
|
|
120
|
+
let t_n = vec3.fromValues(P_n[3], P_n[7], P_n[11]) // 平移向量
|
|
121
|
+
let K_n = mat3.fromValues(parameters[0], 0, 0, 0, parameters[1], 0, parameters[2], parameters[3], 1)
|
|
122
|
+
let p = vec3.add(vec3.create(), vec3.transformMat3(vec3.create(), X, R_n), t_n)
|
|
123
|
+
let pixelCoord = vec3.transformMat3(vec3.create(), vec3.fromValues(p[0] / p[2], p[1] / p[2], 1), K_n)
|
|
124
|
+
if (pixelCoord[0] > 0 && pixelCoord[0] < width && pixelCoord[1] > 0 && pixelCoord[1] < height) {
|
|
125
|
+
let p_vec = vec3.fromValues(...pixelCoord, 1)
|
|
126
|
+
let x_n = vec3.transformMat3(vec3.create(), p_vec, mat3.invert(mat3.create(), K_n))
|
|
127
|
+
let dist = distortPoint(x_n, parameters)
|
|
128
|
+
pixelCoord = vec3.transformMat3(vec3.create(), vec3.fromValues(...dist, 1), K_n)
|
|
129
|
+
predicts[img_id] = [pixelCoord[0], pixelCoord[1]]
|
|
130
|
+
image.predict = {
|
|
131
|
+
uv: [pixelCoord[0], pixelCoord[1]],
|
|
132
|
+
position: {left: pixelCoord[0] * 100 / width, top: pixelCoord[1] * 100 / height},
|
|
133
|
+
distance: Math.hypot(pixelCoord[0] - width / 2, pixelCoord[1] - height / 2)
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
image.predict = undefined
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
return predicts
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function undistortPoint (p, parameters) {
|
|
143
|
+
let K = mat3.fromValues(parameters[0], 0, 0, 0, parameters[1], 0, parameters[2], parameters[3], 1)
|
|
144
|
+
let p_vec = vec3.transformMat3(vec3.create(), vec3.fromValues(...p, 1), mat3.invert(mat3.create(), K)) // 归一化以后的坐标
|
|
145
|
+
let iterators = 100
|
|
146
|
+
let epsilon = 1e-10
|
|
147
|
+
let undistort = vec2.fromValues(p_vec[0], p_vec[1])
|
|
148
|
+
let dist = vec2.subtract(vec2.create(), distortPoint(undistort, parameters), undistort)
|
|
149
|
+
let diff = vec2.subtract(vec2.create(), vec2.add(vec2.create(), undistort, dist), p_vec)
|
|
150
|
+
while((Math.abs(diff[0]) + Math.abs(diff[1])) > epsilon && iterators-- > 0) {
|
|
151
|
+
undistort = vec2.subtract(vec2.create(), p_vec, dist)
|
|
152
|
+
dist = vec2.subtract(vec2.create(), distortPoint(undistort, parameters), undistort)
|
|
153
|
+
diff = vec2.subtract(vec2.create(), vec2.add(vec2.create(), undistort, dist), p_vec)
|
|
154
|
+
}
|
|
155
|
+
return vec3.transformMat3(vec3.create(), vec3.fromValues(...undistort, 1), K)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function distortPoint (p, parameters) {
|
|
159
|
+
let x3 = [p[0], p[1]]
|
|
160
|
+
let r2 = x3[0] * x3[0] + x3[1] * x3[1]
|
|
161
|
+
let r4 = Math.pow(r2, 2)
|
|
162
|
+
let r6 = Math.pow(r2, 3)
|
|
163
|
+
let x3_ = [
|
|
164
|
+
x3[0] * (1 + parameters[4] * r2 + parameters[5] * r4 + parameters[6] * r6) + 2 * parameters[7] * x3[0] * x3[1] + parameters[8] * (r2 + 2 * x3[0] * x3[0]),
|
|
165
|
+
x3[1] * (1 + parameters[4] * r2 + parameters[5] * r4 + parameters[6] * r6) + 2 * parameters[8] * x3[0] * x3[1] + parameters[7] * (r2 + 2 * x3[1] * x3[1])
|
|
166
|
+
]
|
|
167
|
+
return vec2.fromValues(...x3_)
|
|
168
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
0x0000: 'GPSVersionID',
|
|
3
|
+
0x0001: 'GPSLatitudeRef',
|
|
4
|
+
0x0002: 'GPSLatitude',
|
|
5
|
+
0x0003: 'GPSLongitudeRef',
|
|
6
|
+
0x0004: 'GPSLongitude',
|
|
7
|
+
0x0005: 'GPSAltitudeRef',
|
|
8
|
+
0x0006: 'GPSAltitude',
|
|
9
|
+
0x0007: 'GPSTimeStamp',
|
|
10
|
+
0x0008: 'GPSSatellites',
|
|
11
|
+
0x0009: 'GPSStatus',
|
|
12
|
+
0x000A: 'GPSMeasureMode',
|
|
13
|
+
0x000B: 'GPSDOP',
|
|
14
|
+
0x000C: 'GPSSpeedRef',
|
|
15
|
+
0x000D: 'GPSSpeed',
|
|
16
|
+
0x000E: 'GPSTrackRef',
|
|
17
|
+
0x000F: 'GPSTrack',
|
|
18
|
+
0x0010: 'GPSImgDirectionRef',
|
|
19
|
+
0x0011: 'GPSImgDirection',
|
|
20
|
+
0x0012: 'GPSMapDatum',
|
|
21
|
+
0x0013: 'GPSDestLatitudeRef',
|
|
22
|
+
0x0014: 'GPSDestLatitude',
|
|
23
|
+
0x0015: 'GPSDestLongitudeRef',
|
|
24
|
+
0x0016: 'GPSDestLongitude',
|
|
25
|
+
0x0017: 'GPSDestBearingRef',
|
|
26
|
+
0x0018: 'GPSDestBearing',
|
|
27
|
+
0x0019: 'GPSDestDistanceRef',
|
|
28
|
+
0x001A: 'GPSDestDistance',
|
|
29
|
+
0x001B: 'GPSProcessingMethod',
|
|
30
|
+
0x001C: 'GPSAreaInformation',
|
|
31
|
+
0x001D: 'GPSDateStamp',
|
|
32
|
+
0x001E: 'GPSDifferential'
|
|
33
|
+
}
|