«کاشیهای سهبعدی واقعنما» در قالب glTF استاندارد OGC هستند، یعنی میتوانید از هر پردازگری که از مشخصات «کاشیهای سهبعدی OGC» پشتیبانی میکند برای ساختن مصورسازیهای سهبعدی خود استفاده کنید. برای مثال، Cesium یک کتابخانه متنباز بنیادی برای رندر کردن تجسمهای سهبعدی است.
کار با CesiumJS
CesiumJS یک کتابخانه متنباز جاوااسکریپت برای دیداریسازی سهبعدی در وب است. برای اطلاعات بیشتر درباره استفاده از CesiumJS، به آشنایی با CesiumJS مراجعه کنید.
کنترلهای کاربر
پردازنده کاشی CesiumJS دارای مجموعهای استاندارد از کنترلهای کاربر است.
| کنش | شرح |
|---|---|
| نمای پانوراما | کلیک چپ و کشیدن |
| نمای بزرگنمایی | کلیک راست و کشیدن، یا چرخاندن چرخ موشواره |
| چرخاندن نما | Ctrl + کلیک چپ/راست و کشیدن، یا کلیک وسط و کشیدن |
روالهای مطلوب
چندین روش برای کاهش زمان بارگیری سهبعدی CesiumJS وجود دارد. برای مثال:
با افزودن عبارت زیر به HTML پردازشتان، درخواستهای همزمان را فعال کنید:
Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = <REQUEST_COUNT>هرچه
REQUEST_COUNTبالاتر باشد، کاشیها سریعتر بار میشوند. بااینحال، هنگام بار کردن در مرورگر Chrome باREQUEST_COUNTبیشتر از ۱۰ و حافظه نهان غیرفعال، ممکن است با مشکل شناختهشده Chrome مواجه شوید. برای اکثر موارد استفاده،REQUEST_COUNT۱۸ را برای عملکرد بهینه توصیه میکنیم.فعال کردن رد کردن سطوح جزئیات. برای اطلاعات بیشتر، این مشکل Cesium را ببینید.
با فعال کردن showCreditsOnScreen: true، مطمئن شوید که اسناد داده را بهدرستی نمایش میدهید. برای اطلاعات بیشتر، به
خطمشیها مراجعه کنید.
سنجههای پرداز زدن
برای یافتن نرخ فریم، ببینید روش requestAnimationFrame چند بار در ثانیه فراخوانی میشود.
برای دیدن نحوه محاسبه تأخیر فریم، به کلاس PerformanceDisplay نگاهی بیندازید.
نمونههای پردازنده CesiumJS
میتوانید از پردازنده CesiumJS با «کاشیهای سهبعدی» Map Tiles API استفاده کنید، کافی است نشانی وب مجموعه کاشی ریشه را ارائه دهید.
مثال ساده
مثال زیر پردازشگر CesiumJS را مقداردهی اولیه میکند و سپس مجموعه کاشی ریشه را بار میکند.
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>CesiumJS 3D Tiles Simple Demo</title>
<script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
<link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
<div id="cesiumContainer"></div>
<script>
// Enable simultaneous requests.
Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;
// Create the viewer.
const viewer = new Cesium.Viewer('cesiumContainer', {
imageryProvider: false,
baseLayerPicker: false,
geocoder: false,
globe: false,
// https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/#enabling-request-render-mode
requestRenderMode: true,
});
// Add 3D Tiles tileset.
const tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
// This property is needed to appropriately display attributions
// as required.
showCreditsOnScreen: true,
}));
</script>
</body>
برای اطلاعات درباره requestRenderMode،
فعال کردن حالت ارائه درخواست را ببینید.
صفحه HTML بهصورت نشاندادهشده در اینجا پرداز میشود.

یکپارچهسازی Places API (جدید)
میتوانید از CesiumJS با Places API (جدید) برای بازیابی اطلاعات بیشتر استفاده کنید. میتوانید از ابزارک «تکمیل خودکار» برای پرواز به نمای Places استفاده کنید. این مثال از Places Autocomplete API استفاده میکند، که با دنبال کردن این دستورالعملها فعال میشود، و Maps JavaScript API، که با دنبال کردن این دستورالعملها فعال میشود.
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>CesiumJS 3D Tiles Places API (New) Integration Demo</title>
<script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
<link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
<label for="pacViewPlace">Go to a place: </label>
<input
type="text"
id="pacViewPlace"
name="pacViewPlace"
placeholder="Enter a location..."
style="width: 300px"
/>
<div id="cesiumContainer"></div>
<script>
// Enable simultaneous requests.
Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;
// Create the viewer.
const viewer = new Cesium.Viewer("cesiumContainer", {
imageryProvider: false,
baseLayerPicker: false,
requestRenderMode: true,
geocoder: false,
globe: false,
});
// Add 3D Tiles tileset.
const tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
// This property is required to display attributions as required.
showCreditsOnScreen: true,
})
);
const zoomToViewport = (viewport) => {
viewer.entities.add({
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray([
viewport.getNorthEast().lng(), viewport.getNorthEast().lat(),
viewport.getSouthWest().lng(), viewport.getNorthEast().lat(),
viewport.getSouthWest().lng(), viewport.getSouthWest().lat(),
viewport.getNorthEast().lng(), viewport.getSouthWest().lat(),
viewport.getNorthEast().lng(), viewport.getNorthEast().lat(),
]),
width: 10,
clampToGround: true,
material: Cesium.Color.RED,
},
});
viewer.flyTo(viewer.entities);
};
function initAutocomplete() {
const autocomplete = new google.maps.places.Autocomplete(
document.getElementById("pacViewPlace"),
{
fields: [
"geometry",
"name",
],
}
);
autocomplete.addListener("place_changed", () => {
viewer.entities.removeAll();
const place = autocomplete.getPlace();
if (!place.geometry || !place.geometry.viewport) {
window.alert("No viewport for input: " + place.name);
return;
}
zoomToViewport(place.geometry.viewport);
});
}
</script>
<script
async=""
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"
></script>
</body>
نمای چرخشی پهپاد
میتوانید دوربین را کنترل کنید تا در مجموعه کاشیها پویانمایی کند. این پویانمایی، وقتی با Places API (جدید) و Elevation API ترکیب میشود، پرواز تعاملی پهپاد بر فراز هر نقطه موردعلاقه را شبیهسازی میکند.
این نمونه کد شما را در اطراف مکانی که در ابزاره «تکمیل خودکار» انتخاب کردهاید پرواز میدهد.
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>CesiumJS 3D Tiles Rotating Drone View Demo</title>
<script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
<link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
<label for="pacViewPlace">Go to a place: </label>
<input type="text" id="pacViewPlace" name="pacViewPlace" placeholder="Enter a location..." style="width: 300px" />
<div id="cesiumContainer"></div>
<script>
// Enable simultaneous requests.
Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;
// Create the viewer and remove unneeded options.
const viewer = new Cesium.Viewer("cesiumContainer", {
imageryProvider: false,
baseLayerPicker: false,
homeButton: false,
fullscreenButton: false,
navigationHelpButton: false,
vrButton: false,
sceneModePicker: false,
geocoder: false,
globe: false,
infobox: false,
selectionIndicator: false,
timeline: false,
projectionPicker: false,
clockViewModel: null,
animation: false,
requestRenderMode: true,
});
// Add 3D Tile set.
const tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
// This property is required to display attributions.
showCreditsOnScreen: true,
})
);
// Point the camera at a location and elevation, at a viewport-appropriate distance.
function pointCameraAt(location, viewport, elevation) {
const distance = Cesium.Cartesian3.distance(
Cesium.Cartesian3.fromDegrees(
viewport.getSouthWest().lng(), viewport.getSouthWest().lat(), elevation),
Cesium.Cartesian3.fromDegrees(
viewport.getNorthEast().lng(), viewport.getNorthEast().lat(), elevation)
) / 2;
const target = new Cesium.Cartesian3.fromDegrees(location.lng(), location.lat(), elevation);
const pitch = -Math.PI / 4;
const heading = 0;
viewer.camera.lookAt(target, new Cesium.HeadingPitchRange(heading, pitch, distance));
}
// Rotate the camera around a location and elevation, at a viewport-appropriate distance.
let unsubscribe = null;
function rotateCameraAround(location, viewport, elevation) {
if(unsubscribe) unsubscribe();
pointCameraAt(location, viewport, elevation);
unsubscribe = viewer.clock.onTick.addEventListener(() => {
viewer.camera.rotate(Cesium.Cartesian3.UNIT_Z);
});
}
function initAutocomplete() {
const autocomplete = new google.maps.places.Autocomplete(
document.getElementById("pacViewPlace"), {
fields: [
"geometry",
"name",
],
}
);
autocomplete.addListener("place_changed", async () => {
const place = autocomplete.getPlace();
if (!(place.geometry && place.geometry.viewport && place.geometry.location)) {
window.alert(`Insufficient geometry data for place: ${place.name}`);
return;
}
// Get place elevation using the ElevationService.
const elevatorService = new google.maps.ElevationService();
const elevationResponse = await elevatorService.getElevationForLocations({
locations: [place.geometry.location],
});
if(!(elevationResponse.results && elevationResponse.results.length)){
window.alert(`Insufficient elevation data for place: ${place.name}`);
return;
}
const elevation = elevationResponse.results[0].elevation || 10;
rotateCameraAround(
place.geometry.location,
place.geometry.viewport,
elevation
);
});
}
</script>
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"></script>
</body>
ترسیم چندخطیها و برچسبها
این نمونه کد نحوه افزودن چندخطی و برچسب به نقشه را نشان میدهد. میتوانید چندخطی به نقشه اضافه کنید تا مسیرهای رانندگی و پیادهروی را نشان دهید، یا محدودههای ملک را نشان دهید، یا مدت زمان رانندگی و پیادهروی را محاسبه کنید. همچنین میتوانید بدون اینکه صحنه را واقعاً پرداز کنید، مشخصهها را دریافت کنید.
میتوانید کاربران را به گشتوگذار در محلهای ببرید یا املاک همسایه را که درحالحاضر برای فروش هستند نشان دهید، و سپس اشیای سهبعدی مانند بیلبوردها را به صحنه اضافه کنید.
میتوانید سفری را خلاصه کنید و فهرستی از داراییهایی که بازدید کردهاید ارائه دهید و این جزئیات را در اشیای مجازی نمایش دهید.
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>CesiumJS 3D Tiles Polyline and Label Demo</title>
<script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
<link
href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css"
rel="stylesheet"
/>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
// Enable simultaneous requests.
Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;
// Create the viewer.
const viewer = new Cesium.Viewer("cesiumContainer", {
imageryProvider: false,
baseLayerPicker: false,
requestRenderMode: true,
geocoder: false,
globe: false,
});
// Add 3D Tiles tileset.
const tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
// This property is required to display attributions as required.
showCreditsOnScreen: true,
})
);
// Draws a circle at the position, and a line from the previous position.
const drawPointAndLine = (position, prevPosition) => {
viewer.entities.removeAll();
if (prevPosition) {
viewer.entities.add({
polyline: {
positions: [prevPosition, position],
width: 3,
material: Cesium.Color.WHITE,
clampToGround: true,
classificationType: Cesium.ClassificationType.CESIUM_3D_TILE,
},
});
}
viewer.entities.add({
position: position,
ellipsoid: {
radii: new Cesium.Cartesian3(1, 1, 1),
material: Cesium.Color.RED,
},
});
};
// Compute, draw, and display the position's height relative to the previous position.
var prevPosition;
const processHeights = (newPosition) => {
drawPointAndLine(newPosition, prevPosition);
const newHeight = Cesium.Cartographic.fromCartesian(newPosition).height;
let labelText = "Current altitude (meters above sea level):\n\t" + newHeight;
if (prevPosition) {
const prevHeight =
Cesium.Cartographic.fromCartesian(prevPosition).height;
labelText += "\nHeight from previous point (meters):\n\t" + Math.abs(newHeight - prevHeight);
}
viewer.entities.add({
position: newPosition,
label: {
text: labelText,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
pixelOffset: new Cesium.Cartesian2(0, -10),
showBackground: true,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
}
});
prevPosition = newPosition;
};
const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(function (event) {
const earthPosition = viewer.scene.pickPosition(event.position);
if (Cesium.defined(earthPosition)) {
processHeights(earthPosition);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
</script>
</body>
چرخش دوربین
در Cesium، میتوانید دوربین را در مدار نقطهای جالب بچرخانید و از برخورد با ساختمانها جلوگیری کنید. یا میتوانید ساختمانها را شفاف کنید تا وقتی دوربین از میان آنها حرکت میکند، دیده شوند.
ابتدا دوربین را روی نقطهای قفل کنید، سپس میتوانید مدار دوربینی ایجاد کنید تا
داراییتان را بهنمایش بگذارید. میتوانید این کار را بااستفاده از عملکرد
lookAtTransform
دوربین با شنونده رویداد انجام دهید، همانطور که در این نمونه کد نشان داده شده است.
// Lock the camera onto a point.
const center = Cesium.Cartesian3.fromRadians(
2.4213211833389243,
0.6171926869414084,
3626.0426275055174
);
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
viewer.scene.camera.lookAtTransform(
transform,
new Cesium.HeadingPitchRange(0, -Math.PI / 8, 2900)
);
// Orbit around this point.
viewer.clock.onTick.addEventListener(function (clock) {
viewer.scene.camera.rotateRight(0.005);
});
برای اطلاعات بیشتر درباره کنترل دوربین، به کنترل دوربین مراجعه کنید
کار با Cesium برای Unreal
برای استفاده از «افزایه Cesium برای Unreal» با 3D Tiles API، مراحل زیر را دنبال کنید.
افزایه Cesium for Unreal را نصب کنید.
پروژه Unreal جدیدی ایجاد کنید.
به Google Photorealistic 3D Tiles API متصل شوید.
با انتخاب Cesium > Cesium از منو، پنجره Cesium را باز کنید.
Blank 3D Tiles Tileset (مجموعه کاشیهای سهبعدی خالی) را انتخاب کنید.
در World Outliner، پانل جزئیات را با انتخاب این Cesium3DTileset باز کنید.
منبع را از از Cesium Ion به از نشانی وب تغییر دهید.
نشانی وب را روی نشانی وب «کاشیهای سهبعدی Google» تنظیم کنید.
https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY- نمایش اعتبارها در صفحه را فعال کنید تا ارجاعها بهدرستی نمایش داده شوند.
این کار دنیا را بار میکند. برای رفتن به هر LatLng، مورد CesiumGeoreference را در پانل Outliner انتخاب کنید، سپس Origin Latitude/Longitude/Height را در پانل Details ویرایش کنید.
کار کردن با Cesium برای Unity
برای استفاده از کاشیهای واقعگرایانه با Cesium برای Unity، مراحل زیر را دنبال کنید.
پروژه Unity جدیدی ایجاد کنید.
در بخش «مدیر بسته» (ازطریق ویرایشگر > تنظیمات پروژه)، «ثبت محدودشده» جدیدی اضافه کنید.
نام: سزیم
نشانی وب: https://unity.pkg.cesium.com
حوزه(ها): com.cesium.unity
بسته Cesium for Unity را نصب کنید.
به «میانای برنامهسازی کاربردی کاشیهای سهبعدی واقعنمای Google» متصل شوید.
با انتخاب Cesium > Cesium از منو، پنجره Cesium را باز کنید.
روی مجموعه کاشیهای سهبعدی خالی کلیک کنید.
در پانل سمت راست، در گزینه منبع Tileset در بخش منبع، از نشانی وب (بهجای «از Cesium Ion») را انتخاب کنید.
نشانی وب را روی نشانی وب «کاشیهای سهبعدی Google» تنظیم کنید.
https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY- برای نمایش صحیح اسناد، نمایش اسناد در صفحه را فعال کنید.
این دنیا را بار میکند. برای رفتن به هر LatLng، مورد CesiumGeoreference را در سلسلهمراتب صحنه انتخاب کنید، سپس «طول جغرافیایی/عرض جغرافیایی/ارتفاع مبدأ» را در بازرس ویرایش کنید.
کار با deck.gl
deck.gl، که از WebGL بهره میبرد، چارچوب جاوا اسکریپت متنباز برای تجسم دادههای بزرگمقیاس با عملکرد بالا است.
اِسناد
مطمئن شوید که با استخراج فیلد copyright از کاشیهای gltf asset و سپس نمایش آن در نمای رندرشده، ارجاعهای داده را بهدرستی نمایش میدهید. برای
اطلاعات بیشتر، به
نمایش ارجاع دادهها مراجعه کنید.
نمونههای پردازشکننده deck.gl
مثال ساده
مثال زیر موتور پردازش deck.gl را مقداردهی اولیه میکند و سپس مکانی را بهصورت سهبعدی بار میکند. در کدتان، حتماً YOUR_API_KEY را با کلید میانای API واقعیتان جایگزین کنید.
<!DOCTYPE html>
<html>
<head>
<title>deck.gl Photorealistic 3D Tiles example</title>
<script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
<style>
body { margin: 0; padding: 0;}
#map { position: absolute; top: 0;bottom: 0;width: 100%;}
#credits { position: absolute; bottom: 0; right: 0; padding: 2px; font-size: 15px; color: white;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;}
</style>
</head>
<body>
<div id="map"></div>
<div id="credits"></div>
<script>
const GOOGLE_API_KEY = YOUR_API_KEY;
const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
const creditsElement = document.getElementById('credits');
new deck.DeckGL({
container: 'map',
initialViewState: {
latitude: 50.0890,
longitude: 14.4196,
zoom: 16,
bearing: 90,
pitch: 60,
height: 200
},
controller: {minZoom: 8},
layers: [
new deck.Tile3DLayer({
id: 'google-3d-tiles',
data: TILESET_URL,
loadOptions: {
fetch: {
headers: {
'X-GOOG-API-KEY': GOOGLE_API_KEY
}
}
},
onTilesetLoad: tileset3d => {
tileset3d.options.onTraversalComplete = selectedTiles => {
const credits = new Set();
selectedTiles.forEach(tile => {
const {copyright} = tile.content.gltf.asset;
copyright.split(';').forEach(credits.add, credits);
creditsElement.innerHTML = [...credits].join('; ');
});
return selectedTiles;
}
}
})
]
});
</script>
</body>
</html>
مصورسازی لایههای دوبعدی در بالای «کاشیهای سهبعدی واقعنمای Google»
deck.gl TerrainExtension دادههای دوبعدی را روی سطح سهبعدی رندر میکند. برای مثال، میتوانید GeoJSON ردپای ساختمان را روی «هندسه کاشی سهبعدی واقعنما» بپوشانید.
در مثال زیر، لایه ساختمانها با چندضلعیهایی که با سطح «کاشیهای سهبعدی واقعنما» تطبیق داده شدهاند بهتصویر کشیده شده است.
<!DOCTYPE html>
<html>
<head>
<title>Google 3D tiles example</title>
<script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
<style>
body { margin: 0; padding: 0;}
#map { position: absolute; top: 0;bottom: 0;width: 100%;}
#credits { position: absolute; bottom: 0; right: 0; padding: 2px; font-size: 15px; color: white;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;}
</style>
</head>
<body>
<div id="map"></div>
<div id="credits"></div>
<script>
const GOOGLE_API_KEY = YOUR_API_KEY;
const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
const BUILDINGS_URL = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/google-3d-tiles/buildings.geojson'
const creditsElement = document.getElementById('credits');
const deckgl = new deck.DeckGL({
container: 'map',
initialViewState: {
latitude: 50.0890,
longitude: 14.4196,
zoom: 16,
bearing: 90,
pitch: 60,
height: 200
},
controller: true,
layers: [
new deck.Tile3DLayer({
id: 'google-3d-tiles',
data: TILESET_URL,
loadOptions: {
fetch: {
headers: {
'X-GOOG-API-KEY': GOOGLE_API_KEY
}
}
},
onTilesetLoad: tileset3d => {
tileset3d.options.onTraversalComplete = selectedTiles => {
const credits = new Set();
selectedTiles.forEach(tile => {
const {copyright} = tile.content.gltf.asset;
copyright.split(';').forEach(credits.add, credits);
creditsElement.innerHTML = [...credits].join('; ');
});
return selectedTiles;
}
},
operation: 'terrain+draw'
}),
new deck.GeoJsonLayer({
id: 'buildings',
// This dataset is created by CARTO, using other Open Datasets available. More info at: https://3dtiles.carto.com/#about.
data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/google-3d-tiles/buildings.geojson',
stroked: false,
filled: true,
getFillColor: ({properties}) => {
const {tpp} = properties;
// quantiles break
if (tpp < 0.6249)
return [254, 246, 181]
else if (tpp < 0.6780)
return [255, 194, 133]
else if (tpp < 0.8594)
return [250, 138, 118]
return [225, 83, 131]
},
opacity: 0.2,
extensions: [new deck._TerrainExtension()]
})
]
});
</script>
</body>
</html>