map_view_vue2 0.0.2 → 0.0.3
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/README.md +24 -34
- package/index.d.ts +2 -2
- package/map_view_vue2.common.js +370 -300
- package/map_view_vue2.common.js.map +1 -1
- package/map_view_vue2.css +1 -1
- package/map_view_vue2.umd.js +370 -300
- package/map_view_vue2.umd.js.map +1 -1
- package/map_view_vue2.umd.min.js +1 -1
- package/map_view_vue2.umd.min.js.map +1 -1
- package/package.json +5 -13
package/map_view_vue2.common.js
CHANGED
|
@@ -4535,7 +4535,7 @@ es_iterator_constructor_namespaceFn();
|
|
|
4535
4535
|
;// ./node_modules/core-js/modules/es.iterator.map.js
|
|
4536
4536
|
es_iterator_map_namespaceFn();
|
|
4537
4537
|
|
|
4538
|
-
;// ./node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!./node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[3]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/package/map_view/mapView.vue?vue&type=template&id=
|
|
4538
|
+
;// ./node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[1]!./node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[3]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/package/map_view/mapView.vue?vue&type=template&id=4496a68c
|
|
4539
4539
|
|
|
4540
4540
|
|
|
4541
4541
|
var render = function render() {
|
|
@@ -5004,6 +5004,18 @@ var render = function render() {
|
|
|
5004
5004
|
"id": "map"
|
|
5005
5005
|
}
|
|
5006
5006
|
}), _c('div', {
|
|
5007
|
+
directives: [{
|
|
5008
|
+
name: "show",
|
|
5009
|
+
rawName: "v-show",
|
|
5010
|
+
value: _vm.mapLoading,
|
|
5011
|
+
expression: "mapLoading"
|
|
5012
|
+
}],
|
|
5013
|
+
staticClass: "map-loading-mask"
|
|
5014
|
+
}, [_c('span', {
|
|
5015
|
+
staticClass: "map-loading-spinner"
|
|
5016
|
+
}), _c('span', {
|
|
5017
|
+
staticClass: "map-loading-text"
|
|
5018
|
+
}, [_vm._v("地图加载中...")])]), _c('div', {
|
|
5007
5019
|
staticClass: "dialog-overlay",
|
|
5008
5020
|
class: {
|
|
5009
5021
|
show: _vm.loadDialogVisible
|
|
@@ -5583,7 +5595,7 @@ var staticRenderFns = [function () {
|
|
|
5583
5595
|
}, [_vm._v("📥")]), _c('div', [_c('h1', [_vm._v("加载地图")]), _c('p', [_vm._v("选择数据源并加载")])])]);
|
|
5584
5596
|
}];
|
|
5585
5597
|
|
|
5586
|
-
;// ./src/package/map_view/mapView.vue?vue&type=template&id=
|
|
5598
|
+
;// ./src/package/map_view/mapView.vue?vue&type=template&id=4496a68c
|
|
5587
5599
|
|
|
5588
5600
|
;// ./node_modules/core-js/modules/es.array.includes.js
|
|
5589
5601
|
es_array_includes_namespaceFn();
|
|
@@ -64656,10 +64668,6 @@ register(lib);
|
|
|
64656
64668
|
default: false
|
|
64657
64669
|
},
|
|
64658
64670
|
// 是否显示「加载地图」按钮与对话框。
|
|
64659
|
-
showLoader: {
|
|
64660
|
-
type: Boolean,
|
|
64661
|
-
default: true
|
|
64662
|
-
},
|
|
64663
64671
|
// 固定地图查看器宽度:数字按 px,字符串原样使用(如 '600px' / '100%');不传保持铺满父容器。
|
|
64664
64672
|
width: {
|
|
64665
64673
|
type: [String, Number],
|
|
@@ -64688,6 +64696,10 @@ register(lib);
|
|
|
64688
64696
|
// 地图查看器是否已完成初始化。
|
|
64689
64697
|
sidebarWidth: 342,
|
|
64690
64698
|
// 侧栏当前宽度(px),可通过右缘拖拽手柄调整。
|
|
64699
|
+
loadingTasks: 0,
|
|
64700
|
+
// 进行中的数据请求任务数(清单/文件拉取、解析等)。
|
|
64701
|
+
olLoading: false,
|
|
64702
|
+
// OpenLayers 图层渲染中(loadstart ~ loadend 之间)。
|
|
64691
64703
|
dropVisible: false,
|
|
64692
64704
|
// 是否显示全屏拖放遮罩层。
|
|
64693
64705
|
sourceCategory: 'raster',
|
|
@@ -64819,6 +64831,10 @@ register(lib);
|
|
|
64819
64831
|
// 地图画布背景色:传入时覆盖主题默认值(#map 的 background 引用该变量)。
|
|
64820
64832
|
if (this.mapBackground) style['--map-bg'] = this.mapBackground;
|
|
64821
64833
|
return style;
|
|
64834
|
+
},
|
|
64835
|
+
// 是否显示「地图加载中」遮罩:有数据请求任务或图层正在渲染时为 true。
|
|
64836
|
+
mapLoading() {
|
|
64837
|
+
return this.loadingTasks > 0 || this.olLoading;
|
|
64822
64838
|
}
|
|
64823
64839
|
},
|
|
64824
64840
|
// 生命周期:挂载后初始化地图查看器、注册拖放加载与 URL 参数自动加载。
|
|
@@ -64951,6 +64967,13 @@ register(lib);
|
|
|
64951
64967
|
this.updateStatus();
|
|
64952
64968
|
this.$emit('view-changed', this.getViewState());
|
|
64953
64969
|
});
|
|
64970
|
+
// 图层渲染开始 / 结束:瓦片、图片等加载期间显示遮罩。
|
|
64971
|
+
this.map.on('loadstart', () => {
|
|
64972
|
+
this.olLoading = true;
|
|
64973
|
+
});
|
|
64974
|
+
this.map.on('loadend', () => {
|
|
64975
|
+
this.olLoading = false;
|
|
64976
|
+
});
|
|
64954
64977
|
|
|
64955
64978
|
/* ============================================================
|
|
64956
64979
|
* 状态栏 / 提示
|
|
@@ -65112,6 +65135,13 @@ register(lib);
|
|
|
65112
65135
|
this.updateUrlTemplateHint();
|
|
65113
65136
|
this.viewerReady = true;
|
|
65114
65137
|
},
|
|
65138
|
+
// 数据请求任务计数 +1 / -1:遮罩在任务数归零后隐藏。
|
|
65139
|
+
pushLoading() {
|
|
65140
|
+
this.loadingTasks += 1;
|
|
65141
|
+
},
|
|
65142
|
+
popLoading() {
|
|
65143
|
+
this.loadingTasks = Math.max(0, this.loadingTasks - 1);
|
|
65144
|
+
},
|
|
65115
65145
|
// 按住侧栏右缘拖动:mousemove 实时更新宽度,限制在 280 ~ 620px 之间。
|
|
65116
65146
|
startSidebarResize(e) {
|
|
65117
65147
|
e.preventDefault();
|
|
@@ -65957,15 +65987,11 @@ register(lib);
|
|
|
65957
65987
|
if (r1 || r2) throw new Error('不支持带旋转的世界文件');
|
|
65958
65988
|
return [cx - dx / 2, cy + (h - 0.5) * dy, cx + (w - 0.5) * dx, cy - dy / 2];
|
|
65959
65989
|
},
|
|
65960
|
-
//
|
|
65961
|
-
|
|
65962
|
-
|
|
65963
|
-
message: '',
|
|
65964
|
-
options: []
|
|
65965
|
-
};
|
|
65966
|
-
if (!extent) return;
|
|
65990
|
+
// 根据范围数值模式猜测中国高斯投影带号 EPSG(3度带/6度带):命中返回候选码,未命中返回 null。
|
|
65991
|
+
guessCrsFromExtent(extent) {
|
|
65992
|
+
if (!extent) return null;
|
|
65967
65993
|
const x = extent[2];
|
|
65968
|
-
if (x <= 1e6 || x >= 7e7) return;
|
|
65994
|
+
if (x <= 1e6 || x >= 7e7) return null;
|
|
65969
65995
|
const zone = Math.round(x / 1e6);
|
|
65970
65996
|
const options = [];
|
|
65971
65997
|
if (zone >= 25 && zone <= 45) {
|
|
@@ -65980,13 +66006,22 @@ register(lib);
|
|
|
65980
66006
|
label: `6度带 ${zone},中央经线 ${zone * 6 - 3}°E(EPSG:${4491 + zone - 13},含带号)`
|
|
65981
66007
|
});
|
|
65982
66008
|
}
|
|
65983
|
-
if (options.length)
|
|
65984
|
-
|
|
65985
|
-
|
|
65986
|
-
|
|
65987
|
-
|
|
65988
|
-
|
|
65989
|
-
|
|
66009
|
+
if (!options.length) return null;
|
|
66010
|
+
this.form.zoneHint = {
|
|
66011
|
+
message: '检测到高斯带号坐标,可能是:',
|
|
66012
|
+
options
|
|
66013
|
+
};
|
|
66014
|
+
return options[0].code;
|
|
66015
|
+
},
|
|
66016
|
+
// 根据范围推断并提示 UTM/GK 分区对应的 EPSG。
|
|
66017
|
+
updateZoneHint(extent) {
|
|
66018
|
+
this.form.zoneHint = {
|
|
66019
|
+
message: '',
|
|
66020
|
+
options: []
|
|
66021
|
+
};
|
|
66022
|
+
if (!extent) return;
|
|
66023
|
+
const code = this.guessCrsFromExtent(extent);
|
|
66024
|
+
if (code) this.form.dataCrs = code;
|
|
65990
66025
|
},
|
|
65991
66026
|
// 将已选世界文件应用到范围输入框。
|
|
65992
66027
|
applyWorldFileToExtent() {
|
|
@@ -66060,95 +66095,109 @@ register(lib);
|
|
|
66060
66095
|
},
|
|
66061
66096
|
// 加载 PNG/JPG 图片地图(配世界文件或手动填写范围)。
|
|
66062
66097
|
async loadImageLayer() {
|
|
66063
|
-
|
|
66064
|
-
|
|
66065
|
-
|
|
66066
|
-
|
|
66067
|
-
const proj = this.ensureProjection(this.form.dataCrs);
|
|
66068
|
-
if (!proj) {
|
|
66069
|
-
this.toast('请先选择或注册有效的坐标系', 'warn');
|
|
66070
|
-
return;
|
|
66071
|
-
}
|
|
66072
|
-
let {
|
|
66073
|
-
width,
|
|
66074
|
-
height
|
|
66075
|
-
} = this.pickedImage;
|
|
66076
|
-
if (!width || !height) {
|
|
66077
|
-
try {
|
|
66078
|
-
const d = await this.getImageSize(this.pickedImage.objectUrl);
|
|
66079
|
-
width = this.pickedImage.width = d.width;
|
|
66080
|
-
height = this.pickedImage.height = d.height;
|
|
66081
|
-
} catch {
|
|
66082
|
-
this.toast('无法读取图片尺寸', 'err');
|
|
66098
|
+
this.pushLoading();
|
|
66099
|
+
try {
|
|
66100
|
+
if (!this.pickedImage) {
|
|
66101
|
+
this.toast('请先选择图片文件', 'warn');
|
|
66083
66102
|
return;
|
|
66084
66103
|
}
|
|
66085
|
-
|
|
66086
|
-
|
|
66087
|
-
|
|
66088
|
-
|
|
66089
|
-
const layerUrl = URL.createObjectURL(this.pickedImage.file);
|
|
66090
|
-
const layer = new layer_Image({
|
|
66091
|
-
source: new ImageStatic({
|
|
66092
|
-
url: layerUrl,
|
|
66093
|
-
imageExtent: extent,
|
|
66094
|
-
projection: proj
|
|
66095
|
-
})
|
|
66096
|
-
});
|
|
66097
|
-
this.addMapEntry({
|
|
66098
|
-
name: this.pickedImage.name,
|
|
66099
|
-
kind: 'image',
|
|
66100
|
-
layer,
|
|
66101
|
-
extent,
|
|
66102
|
-
projection: proj,
|
|
66103
|
-
cleanup() {
|
|
66104
|
-
URL.revokeObjectURL(layerUrl);
|
|
66104
|
+
const proj = this.ensureProjection(this.form.dataCrs);
|
|
66105
|
+
if (!proj) {
|
|
66106
|
+
this.toast('请先选择或注册有效的坐标系', 'warn');
|
|
66107
|
+
return;
|
|
66105
66108
|
}
|
|
66106
|
-
|
|
66109
|
+
let {
|
|
66110
|
+
width,
|
|
66111
|
+
height
|
|
66112
|
+
} = this.pickedImage;
|
|
66113
|
+
if (!width || !height) {
|
|
66114
|
+
try {
|
|
66115
|
+
const d = await this.getImageSize(this.pickedImage.objectUrl);
|
|
66116
|
+
width = this.pickedImage.width = d.width;
|
|
66117
|
+
height = this.pickedImage.height = d.height;
|
|
66118
|
+
} catch {
|
|
66119
|
+
this.toast('无法读取图片尺寸', 'err');
|
|
66120
|
+
return;
|
|
66121
|
+
}
|
|
66122
|
+
}
|
|
66123
|
+
const er = this.readExtentInputs();
|
|
66124
|
+
if (!er.ok) return;
|
|
66125
|
+
const extent = er.extent;
|
|
66126
|
+
const layerUrl = URL.createObjectURL(this.pickedImage.file);
|
|
66127
|
+
const layer = new layer_Image({
|
|
66128
|
+
source: new ImageStatic({
|
|
66129
|
+
url: layerUrl,
|
|
66130
|
+
imageExtent: extent,
|
|
66131
|
+
projection: proj
|
|
66132
|
+
})
|
|
66133
|
+
});
|
|
66134
|
+
this.addMapEntry({
|
|
66135
|
+
name: this.pickedImage.name,
|
|
66136
|
+
kind: 'image',
|
|
66137
|
+
layer,
|
|
66138
|
+
extent,
|
|
66139
|
+
projection: proj,
|
|
66140
|
+
cleanup() {
|
|
66141
|
+
URL.revokeObjectURL(layerUrl);
|
|
66142
|
+
}
|
|
66143
|
+
});
|
|
66144
|
+
} finally {
|
|
66145
|
+
this.popLoading();
|
|
66146
|
+
}
|
|
66107
66147
|
},
|
|
66108
66148
|
// 从 URL(含可选世界文件或范围参数)创建并加载图片地图。
|
|
66109
66149
|
async addImageFromUrl({
|
|
66110
66150
|
url,
|
|
66111
66151
|
name,
|
|
66112
66152
|
worldText,
|
|
66113
|
-
extent
|
|
66153
|
+
extent,
|
|
66154
|
+
crs = null
|
|
66114
66155
|
}) {
|
|
66115
|
-
|
|
66156
|
+
this.pushLoading();
|
|
66116
66157
|
try {
|
|
66117
|
-
meta
|
|
66118
|
-
} catch {
|
|
66119
|
-
this.toast(`无法加载图片:${url}`, 'err');
|
|
66120
|
-
return;
|
|
66121
|
-
}
|
|
66122
|
-
if (worldText) {
|
|
66158
|
+
let meta;
|
|
66123
66159
|
try {
|
|
66124
|
-
|
|
66125
|
-
|
|
66126
|
-
|
|
66127
|
-
|
|
66160
|
+
meta = await this.getImageSize(url);
|
|
66161
|
+
} catch {
|
|
66162
|
+
this.toast(`无法加载图片:${url}`, 'err');
|
|
66163
|
+
return;
|
|
66128
66164
|
}
|
|
66165
|
+
if (worldText) {
|
|
66166
|
+
try {
|
|
66167
|
+
const wf = this.parseWorldFile(worldText);
|
|
66168
|
+
extent = this.extentFromWorldFile(meta.width, meta.height, wf);
|
|
66169
|
+
} catch (err) {
|
|
66170
|
+
this.toast(`世界文件解析失败:${err.message}`, 'err');
|
|
66171
|
+
}
|
|
66172
|
+
}
|
|
66173
|
+
extent = extent || [0, -meta.height, meta.width, 0];
|
|
66174
|
+
// 坐标系决策:显式传入的 crs 优先;未传入时按范围数值模式猜测高斯带号,未命中默认 EPSG:3857。
|
|
66175
|
+
this.updateZoneHint(extent);
|
|
66176
|
+
const crsCode = crs || this.guessCrsFromExtent(extent) || 'EPSG:3857';
|
|
66177
|
+
this.form.dataCrs = crsCode;
|
|
66178
|
+
const proj = this.ensureProjection(crsCode) || this.PIXEL;
|
|
66179
|
+
const layer = new layer_Image({
|
|
66180
|
+
source: new ImageStatic({
|
|
66181
|
+
url,
|
|
66182
|
+
imageExtent: extent,
|
|
66183
|
+
projection: proj
|
|
66184
|
+
})
|
|
66185
|
+
});
|
|
66186
|
+
this.$refs.extMinx.value = extent[0];
|
|
66187
|
+
this.$refs.extMiny.value = extent[1];
|
|
66188
|
+
this.$refs.extMaxx.value = extent[2];
|
|
66189
|
+
this.$refs.extMaxy.value = extent[3];
|
|
66190
|
+
this.addMapEntry({
|
|
66191
|
+
name,
|
|
66192
|
+
kind: 'image',
|
|
66193
|
+
layer,
|
|
66194
|
+
extent,
|
|
66195
|
+
projection: proj,
|
|
66196
|
+
cleanup() {}
|
|
66197
|
+
});
|
|
66198
|
+
} finally {
|
|
66199
|
+
this.popLoading();
|
|
66129
66200
|
}
|
|
66130
|
-
extent = extent || [0, -meta.height, meta.width, 0];
|
|
66131
|
-
const proj = this.ensureProjection(this.form.dataCrs) || this.PIXEL;
|
|
66132
|
-
const layer = new layer_Image({
|
|
66133
|
-
source: new ImageStatic({
|
|
66134
|
-
url,
|
|
66135
|
-
imageExtent: extent,
|
|
66136
|
-
projection: proj
|
|
66137
|
-
})
|
|
66138
|
-
});
|
|
66139
|
-
this.$refs.extMinx.value = extent[0];
|
|
66140
|
-
this.$refs.extMiny.value = extent[1];
|
|
66141
|
-
this.$refs.extMaxx.value = extent[2];
|
|
66142
|
-
this.$refs.extMaxy.value = extent[3];
|
|
66143
|
-
this.updateZoneHint(extent);
|
|
66144
|
-
this.addMapEntry({
|
|
66145
|
-
name,
|
|
66146
|
-
kind: 'image',
|
|
66147
|
-
layer,
|
|
66148
|
-
extent,
|
|
66149
|
-
projection: proj,
|
|
66150
|
-
cleanup() {}
|
|
66151
|
-
});
|
|
66152
66201
|
},
|
|
66153
66202
|
// 处理用户选择的本地切片目录。
|
|
66154
66203
|
onTilesDirChosen(fileList) {
|
|
@@ -66176,57 +66225,62 @@ register(lib);
|
|
|
66176
66225
|
},
|
|
66177
66226
|
// 加载本地 XYZ 切片目录为瓦片图层。
|
|
66178
66227
|
loadTilesLayer() {
|
|
66179
|
-
|
|
66180
|
-
|
|
66181
|
-
|
|
66182
|
-
|
|
66183
|
-
|
|
66184
|
-
if (!proj) {
|
|
66185
|
-
this.toast('请先选择或注册有效的坐标系', 'warn');
|
|
66186
|
-
return;
|
|
66187
|
-
}
|
|
66188
|
-
const tmap = this.scanTileFiles(this.pickedTiles.files, this.form.tilesTms);
|
|
66189
|
-
if (!tmap.size) {
|
|
66190
|
-
this.toast('所选目录中未找到 z/x/y 结构的切片文件', 'err');
|
|
66191
|
-
return;
|
|
66192
|
-
}
|
|
66193
|
-
const stats = this.tileStats(tmap);
|
|
66194
|
-
// 先按全球网格计算覆盖范围,再据此构建受限网格:
|
|
66195
|
-
// 1) 层级钳制在 [minZ, maxZ],过度缩放时使用最近可用层级(拉伸显示而非空白)
|
|
66196
|
-
// 2) 网格范围限制在实际覆盖区(配合 wrapX:false),缩小时只枚举数据范围内的瓦片,避免卡顿
|
|
66197
|
-
const worldGrid = this.buildTileGrid(proj, stats.maxZ, stats.minZ);
|
|
66198
|
-
const covered = this.tilesExtent(worldGrid, stats);
|
|
66199
|
-
const grid = this.buildTileGrid(proj, stats.maxZ, stats.minZ, covered);
|
|
66200
|
-
const urlCache = new Map();
|
|
66201
|
-
const source = new source_XYZ({
|
|
66202
|
-
projection: proj,
|
|
66203
|
-
tileGrid: grid,
|
|
66204
|
-
wrapX: false,
|
|
66205
|
-
minZoom: stats.minZ,
|
|
66206
|
-
maxZoom: stats.maxZ,
|
|
66207
|
-
tileUrlFunction: ([z, x, y]) => {
|
|
66208
|
-
const f = tmap.get(`${z}/${x}/${y}`);
|
|
66209
|
-
if (!f) return undefined;
|
|
66210
|
-
let u = urlCache.get(f);
|
|
66211
|
-
if (!u) {
|
|
66212
|
-
u = URL.createObjectURL(f);
|
|
66213
|
-
urlCache.set(f, u);
|
|
66214
|
-
}
|
|
66215
|
-
return u;
|
|
66228
|
+
this.pushLoading();
|
|
66229
|
+
try {
|
|
66230
|
+
if (!this.pickedTiles || !this.pickedTiles.files.length) {
|
|
66231
|
+
this.toast('请先选择本地切片XYZ目录', 'warn');
|
|
66232
|
+
return;
|
|
66216
66233
|
}
|
|
66217
|
-
|
|
66218
|
-
|
|
66219
|
-
|
|
66220
|
-
|
|
66221
|
-
layer: new layer_Tile({
|
|
66222
|
-
source
|
|
66223
|
-
}),
|
|
66224
|
-
extent: covered,
|
|
66225
|
-
projection: proj,
|
|
66226
|
-
cleanup() {
|
|
66227
|
-
urlCache.forEach(u => URL.revokeObjectURL(u));
|
|
66234
|
+
const proj = this.ensureProjection(this.form.dataCrs);
|
|
66235
|
+
if (!proj) {
|
|
66236
|
+
this.toast('请先选择或注册有效的坐标系', 'warn');
|
|
66237
|
+
return;
|
|
66228
66238
|
}
|
|
66229
|
-
|
|
66239
|
+
const tmap = this.scanTileFiles(this.pickedTiles.files, this.form.tilesTms);
|
|
66240
|
+
if (!tmap.size) {
|
|
66241
|
+
this.toast('所选目录中未找到 z/x/y 结构的切片文件', 'err');
|
|
66242
|
+
return;
|
|
66243
|
+
}
|
|
66244
|
+
const stats = this.tileStats(tmap);
|
|
66245
|
+
// 先按全球网格计算覆盖范围,再据此构建受限网格:
|
|
66246
|
+
// 1) 层级钳制在 [minZ, maxZ],过度缩放时使用最近可用层级(拉伸显示而非空白)
|
|
66247
|
+
// 2) 网格范围限制在实际覆盖区(配合 wrapX:false),缩小时只枚举数据范围内的瓦片,避免卡顿
|
|
66248
|
+
const worldGrid = this.buildTileGrid(proj, stats.maxZ, stats.minZ);
|
|
66249
|
+
const covered = this.tilesExtent(worldGrid, stats);
|
|
66250
|
+
const grid = this.buildTileGrid(proj, stats.maxZ, stats.minZ, covered);
|
|
66251
|
+
const urlCache = new Map();
|
|
66252
|
+
const source = new source_XYZ({
|
|
66253
|
+
projection: proj,
|
|
66254
|
+
tileGrid: grid,
|
|
66255
|
+
wrapX: false,
|
|
66256
|
+
minZoom: stats.minZ,
|
|
66257
|
+
maxZoom: stats.maxZ,
|
|
66258
|
+
tileUrlFunction: ([z, x, y]) => {
|
|
66259
|
+
const f = tmap.get(`${z}/${x}/${y}`);
|
|
66260
|
+
if (!f) return undefined;
|
|
66261
|
+
let u = urlCache.get(f);
|
|
66262
|
+
if (!u) {
|
|
66263
|
+
u = URL.createObjectURL(f);
|
|
66264
|
+
urlCache.set(f, u);
|
|
66265
|
+
}
|
|
66266
|
+
return u;
|
|
66267
|
+
}
|
|
66268
|
+
});
|
|
66269
|
+
this.addMapEntry({
|
|
66270
|
+
name: this.pickedTiles.name,
|
|
66271
|
+
kind: 'tiles',
|
|
66272
|
+
layer: new layer_Tile({
|
|
66273
|
+
source
|
|
66274
|
+
}),
|
|
66275
|
+
extent: covered,
|
|
66276
|
+
projection: proj,
|
|
66277
|
+
cleanup() {
|
|
66278
|
+
urlCache.forEach(u => URL.revokeObjectURL(u));
|
|
66279
|
+
}
|
|
66280
|
+
});
|
|
66281
|
+
} finally {
|
|
66282
|
+
this.popLoading();
|
|
66283
|
+
}
|
|
66230
66284
|
},
|
|
66231
66285
|
// 更新在线 XYZ URL 模板提示。
|
|
66232
66286
|
updateUrlTemplateHint() {
|
|
@@ -66311,41 +66365,46 @@ register(lib);
|
|
|
66311
66365
|
},
|
|
66312
66366
|
// 根据表单中的在线 XYZ 参数加载图层。
|
|
66313
66367
|
async loadUrlLayer() {
|
|
66314
|
-
|
|
66315
|
-
|
|
66316
|
-
this.
|
|
66317
|
-
|
|
66318
|
-
|
|
66319
|
-
|
|
66320
|
-
|
|
66321
|
-
|
|
66322
|
-
|
|
66323
|
-
|
|
66324
|
-
|
|
66325
|
-
|
|
66326
|
-
|
|
66327
|
-
|
|
66328
|
-
|
|
66329
|
-
|
|
66330
|
-
this.
|
|
66331
|
-
|
|
66332
|
-
|
|
66333
|
-
|
|
66334
|
-
|
|
66335
|
-
|
|
66336
|
-
|
|
66337
|
-
|
|
66338
|
-
|
|
66339
|
-
|
|
66340
|
-
|
|
66341
|
-
|
|
66342
|
-
|
|
66343
|
-
|
|
66344
|
-
this.
|
|
66345
|
-
|
|
66368
|
+
this.pushLoading();
|
|
66369
|
+
try {
|
|
66370
|
+
const template = String(this.form.urlTemplate || '').trim();
|
|
66371
|
+
if (!template) {
|
|
66372
|
+
this.toast('请填写在线切片XYZ URL 模板', 'warn');
|
|
66373
|
+
return;
|
|
66374
|
+
}
|
|
66375
|
+
if (String(this.form.urlMinzoom || '').trim() === '') {
|
|
66376
|
+
this.toast('请填写最小层级', 'warn');
|
|
66377
|
+
return;
|
|
66378
|
+
}
|
|
66379
|
+
if (String(this.form.urlMaxzoom || '').trim() === '') {
|
|
66380
|
+
this.toast('请填写最大层级', 'warn');
|
|
66381
|
+
return;
|
|
66382
|
+
}
|
|
66383
|
+
// 显示范围必填:坐标系可与切片坐标系不同,加载时自动换算
|
|
66384
|
+
const extVals = [this.$refs.urlExtMinx.value, this.$refs.urlExtMiny.value, this.$refs.urlExtMaxx.value, this.$refs.urlExtMaxy.value];
|
|
66385
|
+
if (extVals.some(v => v.trim() === '')) {
|
|
66386
|
+
this.toast('请填写完整的范围:X/Y 的最小值和最大值', 'warn');
|
|
66387
|
+
return;
|
|
66388
|
+
}
|
|
66389
|
+
const nums = extVals.map(Number);
|
|
66390
|
+
if (!nums.every(Number.isFinite)) {
|
|
66391
|
+
this.toast('范围数值无效', 'warn');
|
|
66392
|
+
return;
|
|
66393
|
+
}
|
|
66394
|
+
const extentOpt = {
|
|
66395
|
+
extent: nums,
|
|
66396
|
+
crs: this.form.urlExtentCrs
|
|
66397
|
+
};
|
|
66398
|
+
const r = await this.createXyzEntry(template, this.form.dataCrs, null, null, extentOpt);
|
|
66399
|
+
if (!r.entry) {
|
|
66400
|
+
this.toast(r.error || '加载失败', 'warn');
|
|
66401
|
+
return;
|
|
66402
|
+
}
|
|
66403
|
+
this.form.urlInfo = '使用指定范围加载,已自动定位。';
|
|
66404
|
+
this.addMapEntry(r.entry);
|
|
66405
|
+
} finally {
|
|
66406
|
+
this.popLoading();
|
|
66346
66407
|
}
|
|
66347
|
-
this.form.urlInfo = '使用指定范围加载,已自动定位。';
|
|
66348
|
-
this.addMapEntry(r.entry);
|
|
66349
66408
|
},
|
|
66350
66409
|
// 由图层名生成稳定的矢量默认颜色。
|
|
66351
66410
|
vectorColor(name) {
|
|
@@ -66781,120 +66840,125 @@ register(lib);
|
|
|
66781
66840
|
},
|
|
66782
66841
|
// 解析配对文件、套用 SLD 样式并加载为矢量图层组。
|
|
66783
66842
|
async loadGeoJsonLayers() {
|
|
66784
|
-
|
|
66785
|
-
this.toast('请先选择包含 GeoJSON 文件的目录', 'warn');
|
|
66786
|
-
return;
|
|
66787
|
-
}
|
|
66788
|
-
const format = new format_GeoJSON();
|
|
66789
|
-
const allFeatures = [];
|
|
66790
|
-
let firstProjection = null;
|
|
66791
|
-
let firstCode = null;
|
|
66792
|
-
let sldCount = 0;
|
|
66793
|
-
let orderedPairs = this.pickedGeoJson.pairs;
|
|
66794
|
-
const preparedPairs = [];
|
|
66843
|
+
this.pushLoading();
|
|
66795
66844
|
try {
|
|
66796
|
-
if (this.pickedGeoJson.
|
|
66797
|
-
|
|
66798
|
-
|
|
66799
|
-
this.pickedGeoJson.metadataName = ordered.metadata && ordered.metadata.name ? ordered.metadata.name : null;
|
|
66845
|
+
if (!this.pickedGeoJson || !this.pickedGeoJson.pairs.length) {
|
|
66846
|
+
this.toast('请先选择包含 GeoJSON 文件的目录', 'warn');
|
|
66847
|
+
return;
|
|
66800
66848
|
}
|
|
66801
|
-
|
|
66802
|
-
|
|
66803
|
-
|
|
66804
|
-
|
|
66805
|
-
|
|
66806
|
-
|
|
66807
|
-
|
|
66808
|
-
|
|
66809
|
-
|
|
66810
|
-
|
|
66811
|
-
|
|
66812
|
-
|
|
66813
|
-
|
|
66814
|
-
|
|
66815
|
-
|
|
66816
|
-
|
|
66849
|
+
const format = new format_GeoJSON();
|
|
66850
|
+
const allFeatures = [];
|
|
66851
|
+
let firstProjection = null;
|
|
66852
|
+
let firstCode = null;
|
|
66853
|
+
let sldCount = 0;
|
|
66854
|
+
let orderedPairs = this.pickedGeoJson.pairs;
|
|
66855
|
+
const preparedPairs = [];
|
|
66856
|
+
try {
|
|
66857
|
+
if (this.pickedGeoJson.metadataFile) {
|
|
66858
|
+
const ordered = await this.orderGeoJsonPairsByMetadata(this.pickedGeoJson.pairs, this.pickedGeoJson.metadataFile);
|
|
66859
|
+
orderedPairs = ordered.pairs;
|
|
66860
|
+
this.pickedGeoJson.metadataName = ordered.metadata && ordered.metadata.name ? ordered.metadata.name : null;
|
|
66861
|
+
}
|
|
66862
|
+
for (const pair of orderedPairs) {
|
|
66863
|
+
const text = await pair.file.text();
|
|
66864
|
+
const object = JSON.parse(text);
|
|
66865
|
+
// 优先使用每个文件自身声明的 CRS,避免不同文件坐标系不一致时被统一误读
|
|
66866
|
+
const code = this.geoJsonCrsCode(object) || this.form.dataCrs;
|
|
66867
|
+
const proj = this.ensureProjection(code);
|
|
66868
|
+
if (!proj) throw new Error('无效坐标系:' + code);
|
|
66869
|
+
let features = format.readFeatures(object, {
|
|
66870
|
+
dataProjection: code,
|
|
66871
|
+
featureProjection: code
|
|
66872
|
+
});
|
|
66873
|
+
if (firstProjection) {
|
|
66874
|
+
if (proj.getCode() !== firstCode) {
|
|
66875
|
+
for (const feature of features) {
|
|
66876
|
+
const geometry = feature.getGeometry();
|
|
66877
|
+
if (geometry) geometry.transform(proj, firstProjection);
|
|
66878
|
+
}
|
|
66817
66879
|
}
|
|
66880
|
+
} else {
|
|
66881
|
+
firstProjection = proj;
|
|
66882
|
+
firstCode = proj.getCode();
|
|
66818
66883
|
}
|
|
66819
|
-
|
|
66820
|
-
|
|
66821
|
-
|
|
66822
|
-
|
|
66823
|
-
|
|
66824
|
-
|
|
66825
|
-
|
|
66826
|
-
|
|
66827
|
-
|
|
66828
|
-
|
|
66829
|
-
|
|
66830
|
-
feature.set('_sldStyle', styleFunction, true);
|
|
66831
|
-
if (pair.layerTitle) feature.set('_layerName', pair.layerTitle, true);
|
|
66884
|
+
if (pair.sld) {
|
|
66885
|
+
const sldText = await pair.sld.text();
|
|
66886
|
+
const styles = this.parseSldStyles(sldText);
|
|
66887
|
+
if (styles) {
|
|
66888
|
+
const labelProperty = this.sldLabelProperty(sldText);
|
|
66889
|
+
const styleFunction = this.makeSldStyleFunction(styles, labelProperty);
|
|
66890
|
+
for (const feature of features) {
|
|
66891
|
+
feature.set('_sldStyle', styleFunction, true);
|
|
66892
|
+
if (pair.layerTitle) feature.set('_layerName', pair.layerTitle, true);
|
|
66893
|
+
}
|
|
66894
|
+
sldCount++;
|
|
66832
66895
|
}
|
|
66833
|
-
sldCount++;
|
|
66834
66896
|
}
|
|
66897
|
+
preparedPairs.push({
|
|
66898
|
+
pair,
|
|
66899
|
+
features
|
|
66900
|
+
});
|
|
66901
|
+
allFeatures.push(...features);
|
|
66835
66902
|
}
|
|
66836
|
-
|
|
66837
|
-
|
|
66838
|
-
|
|
66903
|
+
} catch (err) {
|
|
66904
|
+
this.toast('GeoJSON 加载失败:' + err.message, 'err');
|
|
66905
|
+
return;
|
|
66906
|
+
}
|
|
66907
|
+
if (!allFeatures.length) {
|
|
66908
|
+
this.toast('所选文件中没有有效要素', 'warn');
|
|
66909
|
+
return;
|
|
66910
|
+
}
|
|
66911
|
+
// 每个 GeoJSON 独立建层,便于左侧图层列表单独显示/隐藏
|
|
66912
|
+
const layerRecords = preparedPairs.map(({
|
|
66913
|
+
pair,
|
|
66914
|
+
features
|
|
66915
|
+
}) => {
|
|
66916
|
+
const source = this.createReprojectingVectorSource(features, firstProjection);
|
|
66917
|
+
const layer = new Vector({
|
|
66918
|
+
source,
|
|
66919
|
+
style: (feature, resolution) => this.defaultVectorStyle(feature, resolution)
|
|
66839
66920
|
});
|
|
66840
|
-
|
|
66921
|
+
return {
|
|
66922
|
+
name: pair.layerTitle || pair.file.name,
|
|
66923
|
+
kind: 'geojson',
|
|
66924
|
+
layer,
|
|
66925
|
+
featureCount: features.length,
|
|
66926
|
+
visible: true,
|
|
66927
|
+
group: this.inferGeoJsonGroup(features),
|
|
66928
|
+
groupVisible: true,
|
|
66929
|
+
source
|
|
66930
|
+
};
|
|
66931
|
+
});
|
|
66932
|
+
// 缺少 layer 属性的图层归入“0”组;只有确实存在“0”组时才归组,否则保持顶层普通图层
|
|
66933
|
+
if (layerRecords.some(record => record.group === '0')) {
|
|
66934
|
+
for (const record of layerRecords) {
|
|
66935
|
+
if (!record.group) record.group = '0';
|
|
66936
|
+
}
|
|
66841
66937
|
}
|
|
66842
|
-
|
|
66843
|
-
|
|
66844
|
-
return;
|
|
66845
|
-
}
|
|
66846
|
-
if (!allFeatures.length) {
|
|
66847
|
-
this.toast('所选文件中没有有效要素', 'warn');
|
|
66848
|
-
return;
|
|
66849
|
-
}
|
|
66850
|
-
// 每个 GeoJSON 独立建层,便于左侧图层列表单独显示/隐藏
|
|
66851
|
-
const layerRecords = preparedPairs.map(({
|
|
66852
|
-
pair,
|
|
66853
|
-
features
|
|
66854
|
-
}) => {
|
|
66855
|
-
const source = this.createReprojectingVectorSource(features, firstProjection);
|
|
66856
|
-
const layer = new Vector({
|
|
66857
|
-
source,
|
|
66858
|
-
style: (feature, resolution) => this.defaultVectorStyle(feature, resolution)
|
|
66938
|
+
const layer = new Group({
|
|
66939
|
+
layers: layerRecords.map(record => record.layer)
|
|
66859
66940
|
});
|
|
66860
|
-
|
|
66861
|
-
name:
|
|
66941
|
+
this.addMapEntry({
|
|
66942
|
+
name: this.pickedGeoJson.metadataName || this.pickedGeoJson.name,
|
|
66862
66943
|
kind: 'geojson',
|
|
66863
66944
|
layer,
|
|
66864
|
-
|
|
66865
|
-
|
|
66866
|
-
|
|
66867
|
-
|
|
66868
|
-
|
|
66869
|
-
|
|
66870
|
-
|
|
66871
|
-
|
|
66872
|
-
|
|
66873
|
-
|
|
66874
|
-
|
|
66875
|
-
|
|
66945
|
+
sublayers: layerRecords.map(record => {
|
|
66946
|
+
const rest = {
|
|
66947
|
+
...record
|
|
66948
|
+
};
|
|
66949
|
+
delete rest.source;
|
|
66950
|
+
return rest;
|
|
66951
|
+
}),
|
|
66952
|
+
extent: this.featuresExtent(allFeatures),
|
|
66953
|
+
projection: firstProjection,
|
|
66954
|
+
sourceCrs: firstCode,
|
|
66955
|
+
sources: layerRecords.map(record => record.source),
|
|
66956
|
+
cleanup() {}
|
|
66957
|
+
});
|
|
66958
|
+
this.form.geojsonInfo = '已按 metadata.json 顺序加载 ' + orderedPairs.length + ' 个图层、' + allFeatures.length + ' 个要素,应用 ' + sldCount + ' 个 SLD 样式,数据坐标系 ' + firstCode + '。';
|
|
66959
|
+
} finally {
|
|
66960
|
+
this.popLoading();
|
|
66876
66961
|
}
|
|
66877
|
-
const layer = new Group({
|
|
66878
|
-
layers: layerRecords.map(record => record.layer)
|
|
66879
|
-
});
|
|
66880
|
-
this.addMapEntry({
|
|
66881
|
-
name: this.pickedGeoJson.metadataName || this.pickedGeoJson.name,
|
|
66882
|
-
kind: 'geojson',
|
|
66883
|
-
layer,
|
|
66884
|
-
sublayers: layerRecords.map(record => {
|
|
66885
|
-
const rest = {
|
|
66886
|
-
...record
|
|
66887
|
-
};
|
|
66888
|
-
delete rest.source;
|
|
66889
|
-
return rest;
|
|
66890
|
-
}),
|
|
66891
|
-
extent: this.featuresExtent(allFeatures),
|
|
66892
|
-
projection: firstProjection,
|
|
66893
|
-
sourceCrs: firstCode,
|
|
66894
|
-
sources: layerRecords.map(record => record.source),
|
|
66895
|
-
cleanup() {}
|
|
66896
|
-
});
|
|
66897
|
-
this.form.geojsonInfo = '已按 metadata.json 顺序加载 ' + orderedPairs.length + ' 个图层、' + allFeatures.length + ' 个要素,应用 ' + sldCount + ' 个 SLD 样式,数据坐标系 ' + firstCode + '。';
|
|
66898
66962
|
},
|
|
66899
66963
|
// 切换视图坐标系并保持视野位置与比例。
|
|
66900
66964
|
switchViewProjection() {
|
|
@@ -67082,17 +67146,17 @@ register(lib);
|
|
|
67082
67146
|
this.addMapEntry(r.entry);
|
|
67083
67147
|
return r.entry;
|
|
67084
67148
|
},
|
|
67085
|
-
// 从 URL 加载图片地图;可配世界文件(URL 或文本)与范围。
|
|
67149
|
+
// 从 URL 加载图片地图;可配世界文件(URL 或文本)与范围。crs 未传时按范围自动猜测高斯带号,未命中默认 EPSG:3857。
|
|
67086
67150
|
async loadImageFromUrl({
|
|
67087
67151
|
url,
|
|
67088
67152
|
name = null,
|
|
67089
|
-
crs =
|
|
67153
|
+
crs = null,
|
|
67090
67154
|
worldFileUrl = null,
|
|
67091
67155
|
worldText = null,
|
|
67092
67156
|
extent = null
|
|
67093
67157
|
} = {}) {
|
|
67094
67158
|
if (!this.viewerReady) await this.waitViewerReady();
|
|
67095
|
-
this.form.dataCrs = crs;
|
|
67159
|
+
if (crs) this.form.dataCrs = crs;
|
|
67096
67160
|
if (!worldText && worldFileUrl) {
|
|
67097
67161
|
try {
|
|
67098
67162
|
const r = await fetch(worldFileUrl);
|
|
@@ -67103,7 +67167,8 @@ register(lib);
|
|
|
67103
67167
|
url: url,
|
|
67104
67168
|
name: name || decodeURIComponent(String(url).split('/').pop() || '图片'),
|
|
67105
67169
|
worldText: worldText,
|
|
67106
|
-
extent: extent
|
|
67170
|
+
extent: extent,
|
|
67171
|
+
crs: crs
|
|
67107
67172
|
});
|
|
67108
67173
|
return this.activeEntry;
|
|
67109
67174
|
},
|
|
@@ -67250,22 +67315,27 @@ register(lib);
|
|
|
67250
67315
|
},
|
|
67251
67316
|
// 按 autoLoad prop 自动加载数据源:type 为 manifest 加载矢量清单,为 image 加载 PNG 栅格地图。
|
|
67252
67317
|
async processAutoLoad() {
|
|
67253
|
-
|
|
67254
|
-
|
|
67255
|
-
|
|
67256
|
-
|
|
67257
|
-
if (
|
|
67258
|
-
|
|
67259
|
-
|
|
67260
|
-
|
|
67261
|
-
|
|
67262
|
-
|
|
67263
|
-
|
|
67264
|
-
|
|
67265
|
-
|
|
67266
|
-
|
|
67267
|
-
|
|
67268
|
-
|
|
67318
|
+
this.pushLoading();
|
|
67319
|
+
try {
|
|
67320
|
+
const item = this.autoLoad;
|
|
67321
|
+
if (!item || !item.url) return;
|
|
67322
|
+
if (item.type === 'manifest') {
|
|
67323
|
+
const r = await fetch(item.url);
|
|
67324
|
+
if (!r.ok) throw new Error('获取失败:' + item.url + '(' + r.status + ')');
|
|
67325
|
+
const manifest = await r.json();
|
|
67326
|
+
await this.loadGeoJsonFromManifest(manifest, item.baseUrl || '');
|
|
67327
|
+
} else if (item.type === 'image') {
|
|
67328
|
+
await this.loadImageFromUrl({
|
|
67329
|
+
url: item.url,
|
|
67330
|
+
name: item.name || null,
|
|
67331
|
+
crs: item.crs || null,
|
|
67332
|
+
worldFileUrl: item.worldFile || null,
|
|
67333
|
+
worldText: item.worldText || null,
|
|
67334
|
+
extent: item.extent || null
|
|
67335
|
+
});
|
|
67336
|
+
}
|
|
67337
|
+
} finally {
|
|
67338
|
+
this.popLoading();
|
|
67269
67339
|
}
|
|
67270
67340
|
},
|
|
67271
67341
|
// 应用明暗主题(初始值来自 theme prop,切换时发出 theme-changed 事件)。
|