@@ -11,39 +11,42 @@ import {
1111 HeadingPitchRoll ,
1212 Transforms ,
1313 GeoJsonDataSource ,
14- // HeadingPitchRange,
14+ HeadingPitchRange ,
1515 VerticalOrigin ,
1616 HorizontalOrigin ,
1717 HeightReference ,
1818 Color ,
19+ Ion ,
1920 defined ,
20- Cartographic ,
2121 ScreenSpaceEventType ,
2222 ScreenSpaceEventHandler ,
2323} from "cesium" ;
2424import "cesium/Build/Cesium/Widgets/widgets.css" ;
2525import "./style.css" ;
2626
27- // Step 3: Initialize the Cesium Viewer in the HTML element with the
27+ // Step 1.2: Add you default Cesium ion access token
28+ Ion . defaultAccessToken = "your_ion_token_here" ;
29+
30+ // Step 1.3: Initialize the Cesium Viewer in the HTML element with the
2831// `cesiumContainer` ID and visualize terrain
2932const viewer = new Viewer ( "cesiumContainer" , {
3033 terrain : Terrain . fromWorldTerrain ( ) ,
3134 infoBox : false ,
3235} ) ;
3336
34- // Step 4: Add aerial imagery later with labels
37+ // Step 1. 4: Add aerial imagery later with labels
3538viewer . imageryLayers . add (
3639 ImageryLayer . fromWorldImagery ( {
3740 style : IonWorldImageryStyle . AERIAL_WITH_LABELS ,
3841 } ) ,
3942) ;
4043
41- // Step 5: Add Cesium OSM Buildings, a global 3D buildings layer.
44+ // Step 1. 5: Add Cesium OSM Buildings, a global 3D buildings layer.
4245createOsmBuildingsAsync ( ) . then ( ( buildingTileset ) => {
4346 viewer . scene . primitives . add ( buildingTileset ) ;
4447} ) ;
4548
46- // Step 6: Enable lighting the globe, set time of day, and turn on animation sped up 60x
49+ // Step 1. 6: Enable lighting the globe, set time of day, and turn on animation sped up 60x
4750viewer . scene . globe . enableLighting = true ;
4851const customTime = JulianDate . fromDate (
4952 new Date ( Date . UTC ( 2025 , 5 , 10 , 3 , 0 , 0 ) ) ,
@@ -52,19 +55,22 @@ viewer.clock.currentTime = customTime;
5255viewer . clock . shouldAnimate = true ;
5356viewer . clock . multiplier = 60 ;
5457
55- // Step 7: Fly the camera to San Francisco at the given longitude, latitude, and height
58+ // Step 1. 7: Fly the camera to San Francisco at the given longitude, latitude, and height
5659// and orient the camera at the given heading and pitch
57- viewer . camera . flyTo ( {
58- destination : Cartesian3 . fromDegrees ( - 122.4075 , 37.655 , 400 ) ,
59- orientation : {
60- heading : CesiumMath . toRadians ( 310.0 ) ,
61- pitch : CesiumMath . toRadians ( - 10.0 ) ,
62- } ,
63- } ) ;
60+ function setCamera ( ) {
61+ const position = Cartesian3 . fromDegrees ( - 122.4075 , 37.655 , 400 ) ;
62+ const heading = CesiumMath . toRadians ( 310.0 ) ;
63+ const pitch = CesiumMath . toRadians ( - 10.0 ) ;
64+ const range = 250 ;
65+ const hpr = new HeadingPitchRange ( heading , pitch , range ) ;
66+
67+ viewer . camera . lookAt ( position , hpr ) ;
68+ }
69+ setCamera ( ) ;
6470
71+ // Step 2.1: Upload 3D model to the scene
6572const position = Cartesian3 . fromDegrees ( - 122.4875 , 37.705 , 300 ) ;
6673
67- // add glTF model to the scene
6874function addModel ( position ) {
6975 viewer . entities . removeAll ( ) ;
7076
@@ -85,36 +91,40 @@ function addModel(position) {
8591 } ,
8692 } ) ;
8793}
94+ addModel ( position ) ;
95+
96+ // Step 2.2 Upload a GeoJSON to the scene
97+ let geoJsonDataSourceReference ;
8898
8999function addGeoJson ( ) {
90100 // Geojson url for South San Francisco Parks in public data portal https://data-southcity.opendata.arcgis.com/datasets/5851bfc2d1d445e3ac032b0a5f615313_0/explore
91101 const geojsonUrl =
92102 "https://services5.arcgis.com/inY93B27l4TSbT7h/arcgis/rest/services/SSF_Parks/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson" ;
93- //const geojsonUrl = "./src/SSF_Parks.geojson"
94103
95104 GeoJsonDataSource . load ( geojsonUrl , {
96105 clampToGround : true ,
97106 } ) . then ( ( dataSource ) => {
98- console . log ( dataSource ) ;
107+ geoJsonDataSourceReference = dataSource ;
108+
99109 viewer . dataSources . add ( dataSource ) ;
100110 const entities = dataSource . entities . values ;
101111 for ( let i = 0 ; i < entities . length ; i ++ ) {
102112 const entity = entities [ i ] ;
103113
104114 if ( defined ( entity . polygon ) ) {
105- // Style the polygon
106- const color = Color . fromRandom ( {
107- alpha : 1.0 ,
108- } ) ;
109- entity . polygon . material = color ;
115+ // Step 3.1 Style a polygon
116+ const category = entity . properties . Category . getValue ( JulianDate . now ( ) ) ;
110117
111- // Display the label for each polygon
118+ // Step 3.2 Use a color palette
119+ const color = Color . fromCssColorString ( getCategoryColor ( category ) ) ;
120+ entity . polygon . material = color . withAlpha ( 0.6 ) ;
112121 const center = getPolygonCenter ( entity ) ;
113122
123+ // Step 3.3 Add label for a polygon
114124 viewer . entities . add ( {
115125 position : center ,
116126 point : {
117- color : Color . CORNFLOWERBLUE ,
127+ color : color ,
118128 pixelSize : 18 ,
119129 outlineColor : Color . DARKSLATEGREY ,
120130 outlineWidth : 3 ,
@@ -123,7 +133,7 @@ function addGeoJson() {
123133 } ,
124134 label : {
125135 text : entity . properties . FACID ,
126- font : "14pt sans-serif " ,
136+ font : "12pt monospace " ,
127137 heightReference : HeightReference . CLAMP_TO_GROUND ,
128138 horizontalOrigin : HorizontalOrigin . LEFT ,
129139 verticalOrigin : VerticalOrigin . BASELINE ,
@@ -140,6 +150,19 @@ function addGeoJson() {
140150 } ) ;
141151}
142152
153+ function getCategoryColor ( category ) {
154+ const colorMap = {
155+ "Parks – City (developed)" : "#a6cee3" ,
156+ "Parks – City (undeveloped/open space)" : "#1f78b4" ,
157+ "Parks – City (trails)" : "#b2df8a" ,
158+ "Parks (SSFUSD-owned sites)" : "#33a02c" ,
159+ "Parks (other, privately owned)" : "#fb9a99" ,
160+ default : "#CCCCCC" ,
161+ } ;
162+
163+ return colorMap [ category ] || colorMap [ "default" ] ;
164+ }
165+
143166function getPolygonCenter ( entity ) {
144167 const hierarchy = entity . polygon . hierarchy . getValue ( JulianDate . now ( ) ) ;
145168 const positions = hierarchy . positions ;
@@ -156,6 +179,7 @@ function getPolygonCenter(entity) {
156179
157180 return Cartesian3 . divideByScalar ( center , positions . length , new Cartesian3 ( ) ) ;
158181}
182+ addGeoJson ( ) ;
159183
160184function addEntity ( ) {
161185 viewer . entities . add ( {
@@ -167,91 +191,80 @@ function addEntity() {
167191 } ,
168192 } ) ;
169193}
194+ addEntity ( ) ;
170195
171- // function rotateCamera() {
172- // viewer.clock.onTick.addEventListener(function (clock) {
173- // viewer.scene.camera.rotateRight(0.0000001);
174- // viewer.scene.camera.rotateDown(0.0000005);
175- // });
176- // }
177-
178- // function orbitPoint(position) {
179- // const pitch = CesiumMath.toRadians(-15);
180- // const range = 1000.0; // Distance from the point
181- // viewer.scene.preRender.addEventListener(function(scene, time) {
182- // const delta = JulianDate.secondsDifference(time, viewer.clock.startTime);
183- // const newHeading = CesiumMath.toRadians(delta/5); // degrees/sec
184-
185- // viewer.camera.lookAt(position, new HeadingPitchRange(newHeading, pitch, range));
186- // });
187- // }
196+ // Step 3.5 Handle Custom Picking
188197
189198function addCustomPicking ( ) {
190199 const entity = viewer . entities . add ( {
191200 label : {
192201 show : false ,
193202 showBackground : true ,
194203 font : "14px monospace" ,
195- verticalOrigin : VerticalOrigin . BOTTOM ,
196204 heightReference : HeightReference . CLAMP_TO_GROUND ,
197- disableDepthTestDistance : Number . POSITIVE_INFINITY ,
205+ pixelOffset : new Cartesian2 ( 0 , - 50 ) ,
198206 } ,
199207 } ) ;
200208
201- // Mouse over the globe to see the cartographic position
202209 const handler = new ScreenSpaceEventHandler ( viewer . scene . canvas ) ;
210+
211+ // If the mouse is over a geojson entity from the parks dataset, show a label
203212 handler . setInputAction ( function ( movement ) {
204- const cartesian = viewer . scene . pickPosition ( movement . endPosition ) ;
205- if ( cartesian ) {
206- const cartographic = Cartographic . fromCartesian ( cartesian ) ;
207- const longitudeString = CesiumMath . toDegrees (
208- cartographic . longitude ,
209- ) . toFixed ( 2 ) ;
210- const latitudeString = CesiumMath . toDegrees (
211- cartographic . latitude ,
212- ) . toFixed ( 2 ) ;
213- const heightString = cartographic . height . toFixed ( 2 ) ;
214-
215- entity . position = cartesian ;
216- entity . label . show = true ;
217- entity . label . text =
218- `Lon: ${ ` ${ longitudeString } ` . slice ( - 7 ) } \u00B0` +
219- `\nLat: ${ ` ${ latitudeString } ` . slice ( - 7 ) } \u00B0` +
220- `\nAlt: ${ ` ${ heightString } ` . slice ( - 7 ) } m` ;
221- } else {
222- entity . label . show = false ;
213+ const pickedObject = viewer . scene . pick ( movement . endPosition ) ;
214+
215+ if ( defined ( pickedObject ) && defined ( pickedObject . id ) ) {
216+ if ( geoJsonDataSourceReference . entities . contains ( pickedObject . id ) ) {
217+ const cartesian = getPolygonCenter ( pickedObject . id ) ;
218+ entity . position = cartesian ;
219+ entity . label . show = true ;
220+
221+ const parkType = pickedObject . id . properties . Class . getValue (
222+ JulianDate . now ( ) ,
223+ ) ;
224+ const acreage = pickedObject . id . properties . Acres . getValue (
225+ JulianDate . now ( ) ,
226+ ) ;
227+ entity . label . text = `Park Type: ${ parkType } ` + `\nAcres: ${ acreage } ` ;
228+ return ;
229+ }
223230 }
231+ entity . label . show = false ;
224232 } , ScreenSpaceEventType . MOUSE_MOVE ) ;
233+ }
234+ addCustomPicking ( ) ;
225235
226- // let lastSelectedEntity;
227- // let lastEntityColor;
228-
229- // const handler = new ScreenSpaceEventHandler(viewer.scene.canvas);
230- // handler.setInputAction(function (movement) {
231- // const pickedObject = viewer.scene.pick(movement.position);
232- // console.log(pickedObject.id)
233- // if (defined(pickedObject)) {
234-
235- // if (pickedObject.id.id !== lastSelectedEntity) {
236- // //lastSelectedEntity
237-
238- // }
239-
240- // pickedObject.id.polygon.material = Color.BLACK
241- // }
242-
243- // entity.billboard.scale = 2.0;
244- // entity.billboard.color = Cesium.Color.YELLOW;
245- // } else {
246- // entity.billboard.scale = 1.0;
247- // entity.billboard.color = Cesium.Color.WHITE;
248- // }
249- // }, ScreenSpaceEventType.LEFT_CLICK);
236+ // Step 4.2 Orbit a point when user holds down the Q key
237+ let orbitHandler ;
238+
239+ function toggleOrbit ( position ) {
240+ if ( ! defined ( orbitHandler ) ) {
241+ orbitHandler = function ( scene , time ) {
242+ const pitch = CesiumMath . toRadians ( - 15 ) ;
243+ const range = 1000.0 ; // Distance from the point
244+ const delta = JulianDate . secondsDifference ( time , viewer . clock . startTime ) ;
245+ const newHeading = CesiumMath . toRadians ( delta / 5 ) ; // degrees/sec
246+
247+ viewer . camera . lookAt (
248+ position ,
249+ new HeadingPitchRange ( newHeading , pitch , range ) ,
250+ ) ;
251+ } ;
252+ viewer . scene . preRender . addEventListener ( orbitHandler ) ;
253+ } else {
254+ viewer . scene . preRender . removeEventListener ( orbitHandler ) ;
255+ orbitHandler = undefined ;
256+ setCamera ( ) ;
257+ }
250258}
251259
252- addModel ( position ) ;
253- addGeoJson ( ) ;
254- addEntity ( ) ;
255- //rotateCamera()
256- //orbitPoint(position)
257- addCustomPicking ( ) ;
260+ document . addEventListener (
261+ "keydown" ,
262+ function ( e ) {
263+ if ( typeof e . code !== "undefined" ) {
264+ if ( e . code === "KeyQ" ) {
265+ toggleOrbit ( position ) ;
266+ }
267+ }
268+ } ,
269+ false ,
270+ ) ;
0 commit comments