0%

asp.net core 發佈 cesium terrain

 

terrain 資料產生

首先下載 2016 年全臺灣 20 公尺網格 DTM 資料
接著到 cesium-terrain-builder-docker pull image
也可以直接拿他給的 docker file 來編譯

後來發現 2016 失效了 , 現在改 2020
然後解出來後是一個 csv 然後又帶一個連結 , 實在無言 XD
下面的 command 就自己依照新的 2020 去調整吧 , 剛好手上沒 linux 機器就不弄啦

host

1
2
3
4
5
cd ~
docker pull tumgis/ctb-quantized-mesh:latest
mkdir cesium
cd cesium
docker run -it -v ${PWD}:/data 1d5e

container

1
2
3
4
5
6
7
8
9
10
11
12
13
apt install wget
wget -O tw.zip https://www.tgos.tw/TGOS/Generic/Utility/Filedownload.ashx?url=https://www.tgos.tw:443/TGOS/VirtualDir/MAPData/10749/Download/%E4%B8%8D%E5%88%86%E5%B9%85_%E5%85%A8%E5%8F%B0%E5%8F%8A%E6%BE%8E%E6%B9%96.zip
apt install unzip
unzip tw.zip

#注意他這邊 -o 要先有資料夾不會幫你自動建立
mkdir terrain

#生成 layer.json
ctb-tile -f Mesh -C -N -l -o terrain dem_20m.tif

#生成 terrain 檔
ctb-tile -f Mesh -C -N -o terrain dem_20m.tif

舊版 .net

記得多年前曾經做過 terrain 費了很大的力氣 , 剛好在寫 .net core 就順手改寫看看 , 太久沒做應該還要多補寫怎麼製作 terrain , 先偷懶之後有時間再補
以前好像是在 IIS 上面直接掛載以下這段 code 的模組 , 就可以發出 terrain , 參考強國人的網站已找不到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ZipHeaderModule : IHttpModule
{
public void Dispose()
{
//do nothing
//throw new NotImplementedException();
}

public void Init(HttpApplication context)
{
context.EndRequest += Context_EndRequest;
}

private void Context_EndRequest(object sender, EventArgs e)
{
var context = sender as HttpApplication;
string fileExtension = context.Request.CurrentExecutionFilePathExtension;
if (fileExtension.Length >= 8)
{
if (fileExtension.Substring(0, 8) == ".terrain")
{
context.Response.AddHeader("Content-Encoding", "gzip");
}
}

}
}

參考 cesium 網站的 example 還要加上一狗票 web.config 設定 , 最關鍵就是這句 <mimeMap fileExtension=".terrain" mimeType="application/vnd.quantized-mesh" />

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<remove fileExtension=".czml" />
<mimeMap fileExtension=".czml" mimeType="application/json" />
<remove fileExtension=".glsl" />
<mimeMap fileExtension=".glsl" mimeType="text/plain" />
<remove fileExtension=".b3dm" />
<mimeMap fileExtension=".b3dm" mimeType="application/octet-stream" />
<remove fileExtension=".pnts" />
<mimeMap fileExtension=".pnts" mimeType="application/octet-stream" />
<remove fileExtension=".i3dm" />
<mimeMap fileExtension=".i3dm" mimeType="application/octet-stream" />
<remove fileExtension=".cmpt" />
<mimeMap fileExtension=".cmpt" mimeType="application/octet-stream" />
<remove fileExtension=".gltf" />
<mimeMap fileExtension=".gltf" mimeType="model/gltf+json" />
<remove fileExtension=".bgltf" />
<mimeMap fileExtension=".bgltf" mimeType="model/gltf-binary" />
<remove fileExtension=".glb" />
<mimeMap fileExtension=".glb" mimeType="model/gltf-binary" />
<remove fileExtension=".json" />
<mimeMap fileExtension=".json" mimeType="application/json" />
<remove fileExtension=".geojson" />
<mimeMap fileExtension=".geojson" mimeType="application/json" />
<remove fileExtension=".topojson" />
<mimeMap fileExtension=".topojson" mimeType="application/json" />
<remove fileExtension=".wasm" />
<mimeMap fileExtension=".wasm" mimeType="application/wasm" />
<remove fileExtension=".woff" />
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
<remove fileExtension=".woff2" />
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
<remove fileExtension=".kml" />
<mimeMap fileExtension=".kml" mimeType="application/vnd.google-earth.kml+xml" />
<remove fileExtension=".kmz" />
<mimeMap fileExtension=".kmz" mimeType="application/vnd.google-earth.kmz" />
<remove fileExtension=".svg" />
<mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
<remove fileExtension=".terrain" />
<mimeMap fileExtension=".terrain" mimeType="application/vnd.quantized-mesh" />
<remove fileExtension=".ktx" />
<mimeMap fileExtension=".ktx" mimeType="image/ktx" />
<remove fileExtension=".crn" />
<mimeMap fileExtension=".crn" mimeType="image/crn" />
</staticContent>
</system.webServer>
</configuration>

新版 .net core

起初的想法是掛個 Middleware 應該就可以快速搞定 , 研究了下其實 UseStaticFiles 本身就是個 Middleware , 有幫我們準備好現成的方法可以做微調
關鍵就是設定 .terrain 讓他 mapping 至 application/vnd.quantized-mesh
接著在 Response Header 加上 Content-Encoding 讓他是採用 gzip 就搞定了!
最後因為是要給前端使用 , 所以必須補上 CORS 這個討人厭的鬼東西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment( ))
{
app.UseDeveloperExceptionPage( );
}

app.UseCors( builder =>
{
builder.AllowAnyOrigin( );
builder.AllowAnyMethod( );
builder.AllowAnyHeader( );
} );

app.UseRouting( );

var provider = new FileExtensionContentTypeProvider( );
provider.Mappings.Add( ".terrain", "application/vnd.quantized-mesh" );

app.UseStaticFiles( new StaticFileOptions
{
ContentTypeProvider = provider,
OnPrepareResponse = ctx =>
{
string extension = System.IO.Path.GetExtension(ctx.File.Name);
if (extension == ".terrain")
{
ctx.Context.Response.Headers.Add( "Content-Encoding", "gzip" );
}
},
} );

app.UseAuthorization( );

app.UseEndpoints( endpoints =>
{
endpoints.MapControllers( );
} );
}

最後前端會打出類似這樣的網址 http://localhost:5000/terrain/10/1715/645.terrain?v=1.1.0
Response 會給出以下這樣的訊息就算是搞定了
特別注意到 Access-Control-Allow-Origin: * Content-Encoding: gzip Content-Type: application/vnd.quantized-mesh 這三個部分缺一不可

1
2
3
4
5
6
7
8
9
Accept-Ranges: bytes
Access-Control-Allow-Origin: *
Content-Encoding: gzip
Content-Length: 2608
Content-Type: application/vnd.quantized-mesh
Date: Sun, 25 Apr 2021 19:16:35 GMT
ETag: "1d4f43d3df16cb0"
Last-Modified: Tue, 16 Apr 2019 10:14:57 GMT
Server: Kestrel

最後補上一個前端 index.html 就全部都搞定了

最後一步下載 Cesium
將 Build 裡面的 CesiumUnminified 丟到你專案內的 wwwroot 資料夾內
並且把開頭算好的 terrain 資料夾也丟到 wwwroot 裡面
並且把 CesiumUnminifiedterrain 都設定 Exclude from project 防止太多檔案導致 visual studio 速度變慢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<!DOCTYPE html>
<html lang="zh-tw">

<head>
<meta charset="UTF-8">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
<!--for iphone-->
<meta name="viewport"
content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">

<!--強制防止cache-->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="Pragma" content="no-cache" />

<meta http-equiv="X-UA-Compatible" content="ie=edge">

<title>Cesium</title>

<style>
@import url(CesiumUnminified/Widgets/widgets.css);

* {
margin: 0;
padding: 0;
list-style: none;
font-family: '微軟正黑體', 'Microsoft JhengHei', sans-serif;
box-sizing: border-box;
}

html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}

</style>
</head>

<body>
<!--地圖主體-->
<div id="cesiumContainer"></div>

<!--Cesium-->
<script src="./CesiumUnminified/Cesium.js"></script>

<script>
var viewer = new Cesium.Viewer('cesiumContainer', {
animation: false, //是否創建動畫小器件,左下角儀表
baseLayerPicker: false, //是否顯示圖層選擇器
fullscreenButton: false, //是否顯示全屏按鈕
geocoder: false, //是否顯示geocoder小器件,右上角查詢按鈕
homeButton: false, //是否顯示Home按鈕
infoBox: false, //是否顯示信息框
sceneModePicker: false, //是否顯示3D/2D選擇器
selectionIndicator: false, //是否顯示選取指示器組件
timeline: false, //是否顯示時間軸
navigationHelpButton: false, //是否顯示右上角的幫助按鈕
scene3DOnly: true, //如果設置為true,則所有幾何圖形以3D模式繪製以節約GPU資源
clock: new Cesium.Clock(), //用於控制當前時間的時鐘對象
selectedImageryProviderViewModel: undefined, //當前圖像圖層的顯示模型,僅baseLayerPicker設為true有意義
imageryProviderViewModels: Cesium
.createDefaultImageryProviderViewModels(), //可供BaseLayerPicker選擇的圖像圖層ProviderViewModel數組
selectedTerrainProviderViewModel: undefined, //當前地形圖層的顯示模型,僅baseLayerPicker設為true有意義
terrainProviderViewModels: Cesium
.createDefaultTerrainProviderViewModels(), //可供BaseLayerPicker選擇的地形圖層ProviderViewModel數組
imageryProvider: new Cesium.OpenStreetMapImageryProvider({
}), //圖像圖層提供者,僅baseLayerPicker設為false有意義
fullscreenElement: document.body, //全屏時渲染的HTML元素,
useDefaultRenderLoop: true, //如果需要控制渲染循環,則設為true
targetFrameRate: undefined, //使用默認render loop時的幀率
showRenderLoopErrors: false, //如果設為true,將在一個HTML面板中顯示錯誤信息
automaticallyTrackDataSourceClocks: true, //自動追踪最近添加的數據源的時鐘設置
contextOptions: undefined, //傳遞給Scene對象的上下文參數(scene.options)
sceneMode: Cesium.SceneMode.SCENE3D, //初始場景模式
mapProjection: new Cesium.WebMercatorProjection(), //地圖投影體系
dataSources: new Cesium.DataSourceCollection()
//需要進行可視化的數據源的集合
});

//設定相機位置

viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(121, 22, 200000.0),
orientation: {
heading: Cesium.Math.toRadians(10.0),
pitch: Cesium.Math.toRadians(-15.0),
roll: 0
}
});

viewer.camera.position = {
"x": -4225970.254191063,
"y": 6609503.858450623,
"z": 1614300.9176620042
}

viewer.camera.direction = {
"x": 0.649115806651835,
"y": -0.6057112854818547,
"z": 0.4601766054404077
}

viewer.camera.up = {
"x": -0.06293426272291522,
"y": 0.5601105054909595,
"z": 0.8260239101952105
}

viewer.camera.right = {
"x": -0.7580817555714294,
"y": -0.5651460521657928,
"z": 0.3254565894111815
}


var scene = viewer.scene;



var terrainProvider = new Cesium.CesiumTerrainProvider({
url: 'http://localhost:5000/terrain'
});
//default terrain
scene.terrainProvider = terrainProvider;

</script>
</body>
</html>

nodejs

如果要在 nodejs 發的話只要設定 static 裡面的 setHeaders 去定義即可 , 原理都一樣
比較雷的是設定時常常忘記加上 / 開頭 XD
可以看官方說明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const express = require('express')
const path = require('path')
const app = express()
const port = 5000

app.use('/CesiumUnminified', express.static(__dirname + '/CesiumUnminified'))

var options = {
dotfiles: 'ignore',
// etag: false,
extensions: ['terrain', 'json'],
// index: false,
// maxAge: '1d',
// redirect: false,
setHeaders: function (res, p, stat) {
let ext = path.parse(p).ext
if (ext === '.terrain') {
console.log('is terrain')
res.set('.terrain' , 'application/vnd.quantized-mesh')
res.set('Content-Encoding', 'gzip')
}
}
}


app.use('/terrain', express.static(__dirname + '/terrain', options))


app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, '/index.html'));
});

app.listen(port, () => {
console.log(`app listening on port ${port}`)
})

nestjs

最近剛好又看到 nestjs 就順便玩看看 helloworld 直接上 terrain 有點硬阿 XD
整個玩起來就是 angular + express 的 fu

1
2
3
npm i -g @nestjs/cli
nest new test
nest --help

一上來就直接陣亡因為對 .eslintrc.js 設定不是很熟狂噴紅字 , 所以先關閉確保我能正常工作

1
ignorePatterns: ['*'],

建立一個 demo 的 controller

1
nest g co

程式碼如下

1
2
3
4
5
6
7
8
9
10
import { Controller, Get, Post, Req, Res } from '@nestjs/common';
// import { Request, Response } from 'express'

@Controller('demo')
export class DemoController {
@Get()
index(@Res() res){
res.sendFile('index.html', { root: '.' })
}
}

接著加入 index.html CesiumUnminified terrain 然後檔案結構大概這樣

1
2
3
4
CesiumUnminified
src
terrain
index.html

接著安裝 serve-static

1
npm install --save @nestjs/serve-static

最後找到 app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DemoController } from './demo/demo.controller';
import { join, parse } from 'path';

import { Request, Response } from 'express'
import { ServeStaticModule } from '@nestjs/serve-static';

@Module({
imports: [

ServeStaticModule.forRoot({
serveRoot: '/CesiumUnminified',
rootPath: join(__dirname, '..', 'CesiumUnminified'),
}),
//for terrain
ServeStaticModule.forRoot({
serveRoot: '/terrain',
rootPath: join(__dirname, '..', 'terrain'),
serveStaticOptions: {
extensions: ['terrain', 'json', 'txt'],
setHeaders: function (res: Response, p, stat) {
let ext = parse(p).ext
console.log(ext)
if (ext === '.terrain') {
console.log('is terrain')
res.set('.terrain', 'application/vnd.quantized-mesh')
res.set('Content-Encoding', 'gzip')
}
}
}
})
],
controllers: [AppController, DemoController],
providers: [AppService],
})
export class AppModule { }

然後就可以執行看看

1
npm run start:dev

後來發現要在 vscode debug 可以這樣設定
建立 .vscode 資料夾 , 然後建立 .launch.json
接著點選蟲蟲圖示 debug
詳細可以看這三篇 , 實在太累啦就懶得 format 了

https://dev.to/gentax/nestjs-right-settings-for-debugging-kl0
https://medium.com/@abhishek2kr/how-to-debug-nestjs-application-in-vscode-74379618760f
https://www.youtube.com/watch?v=QL3KXE1hOgA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Nest Framework",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"start:debug",
"--",
"--inspect-brk"
],
"autoAttachChildProcesses": true,
"restart": true,
"sourceMaps": true,
"stopOnEntry": false,
"console": "integratedTerminal",
}
]
}
關閉