最近摸緊Cesium同埋工作關係都有機會接觸CSDI
為左熟習Cesium有咩功能所以就有呢個exercise
我會點形容Cesium呢,就係一個open platform 專係做宏觀既3D viewer
唔同Autodesk forge 做唔到一d open world既demo
所以(應該)好適合做city planning
而CSDI 就係LandsD 既一個database 收集左好多唔同部門provide既五花八門既asset data
例如廁所,停車場資料,學校,仲有好多意想不到既野
但佢有樣野唔好,就係data 既attribute format 好明顯冇夾過就草草咁入資料落去
對programmer 唔friendly
入返正題先,首先拎左香港既公廁資料先,
入去CSDI個網
search toilet 但搵唔到有用既dataset
公廁應該歸食環境署管既,search by department
仲唔搵到你
click download,要csv format,貪佢夠哂簡單,
正式用cesium 之前做多野樣,就係轉佢做json format
寫code直接read csv 最parse做json都得但唔係今次重點,
所以最後用左online csv to json converter
之後就黎到Cesium果part
如果只係做lab心態既話cesium 其實有個default api key已經做到好多野
同以往一樣,用nextjs, cesium 有cesium 既library, 另外就有隻叫resium既react support
npx create-next-app@latest
npm install --save cesium resium
裝完之後就可以寫code
寫左個底食左個json file 先
const file = await fs.readFile(process.cwd() + '/app/toilet.json', 'utf8');
const data = JSON.parse(file);
load resium library
const Cesium = dynamic(
() => import('../components/Cesium'),
{ ssr: false }
)
就係咁簡單
import dynamic from 'next/dynamic'
import { promises as fs } from 'fs';
const Cesium = dynamic(
() => import('../components/Cesium'),
{ ssr: false }
)
export default async function Home() {
const file = await fs.readFile(process.cwd() + '/app/toilet.json', 'utf8');
const data = JSON.parse(file);
return (
<div className="h-screen w-screen">
<Cesium data={data} />
</div>
)
}
之後寫Cesium呢個custom components
其實就係直接call resium入面既components
<Viewer
style={{ height: "100%" }}
terrainProvider={terrainProvider}
ref={(e) => {
viewer = (e && e.cesiumElement)!;
}}
>
<Cesium3DTileset url={IonResource.fromAssetId(96188)} />
</Viewer>
裡面有行
<Cesium3DTileset url={IonResource.fromAssetId(96188)} />
其實就係直接係Cesium Ion 既library度去拎個asset id = 96188 既model 出黎
96188 就係cesium 自己既3D building tiles
run黎試下就會有呢個畫面
之後處理返csdi既數據
睇返佢提供既data spec
可以用SEARCH02_EN黎做filtering
const toiletData = data
.filter((item) => item["SEARCH02_EN"] === "PUBLIC TOILETS")
.sort((a, b) =>
(a["SEARCH01_TC"] as string).localeCompare(b["SEARCH01_TC"] as string)
);
另外仲有lat 同long既數值 咁就可以create 返toilet既marker
{toiletData.map((item, index) => (
<Entity
key={index}
onClick={() => {
const point = Cartesian3.fromDegrees(
parseFloat(item["LONGITUDE"]),
parseFloat(item["LATITUDE"]),
100
);
viewer.camera.flyTo({ destination: point });
}}
name={item["NAME_TC"]}
point={{ pixelSize: 20 }}
description={item["ADDRESS_TC"]}
position={Cartesian3.fromDegrees(
parseFloat(item["LONGITUDE"]),
parseFloat(item["LATITUDE"]),
100
)}
/>
))}
再加多少少wrap up
成品如下