Commit c0f2ef47 authored by Rushikesh Padsala's avatar Rushikesh Padsala
Browse files

added HFT viewer

parent f67ddac7
<html><head>
<title>CO2 Sensor</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
<style>
table {
border: 0px;
padding: 0px;
margin: 0px;
border-collapse: collapse;
}
td {
border-right: 1px solid black;
padding: 2px 2px 5px 2px;
margin: 0px;
}
th {
border-bottom: 1px solid black;
padding: 5px;
margin: 0px;
}
tr {
padding: 0px;
margin: 0px;
}
.btn-very-sm {
padding: 5px;
font-size: 12px;
border-radius: 7px;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
</head>
<body>
<!-- <div class="container">
<div class="px-4 py-5 my-5 text-center">
<img class="d-block mx-auto mb-4" src="index_covidsta.png" alt="" width="400">
<!-- <h1 class="display-5 fw-bold">Centered hero</h1> -->
<!-- <div class="col-lg-6 mx-auto"> -->
<!-- <p class="lead mb-4">The SensorThings API server for COVID-19 Case Statistics.</p> -->
<!-- <div class="d-grid gap-2 d-sm-flex justify-content-sm-center"> -->
<!-- <button type="button" class="btn btn-primary btn-lg px-4 gap-3">To COVID STA Server</button> -->
<!-- <a type="button" class="btn btn-primary btn-lg px-4 gap-3" href="v1.1"> -->
<!-- <i class="bi bi-hdd"></i> To COVID STA Server</a> -->
<!-- <a type="button" class="btn btn-outline-secondary btn-lg px-4 gap-3" href="https://covid19dashboard.org"> <i class="bi bi-graph-up"></i> COVID-19 Dashboard</a> -->
<!-- <a type="button" class="btn btn-outline-secondary btn-lg px-4 gap-3"
href="https://inspire.ec.europa.eu/good-practice/ogc-sensorthings-api-inspire-download-service"
target="_blank">More from STA</a> -->
<!-- <button type="button" class="btn btn-outline-secondary btn-lg px-4">Secondary</button> -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
<!-- <img src="index_covidsta.png" alt="" width="400px">
<h4 class="text-muted">The SensorThings API server for COVID-19 Case Statistics.</h4>
<hr>
<h4>Important Link</h4>
<a type="button" class="btn btn-primary" href="v1.1">To COVID STA</a> <br><br>
<hr> -->
<h4>Quick Start</h4>
<div class="list-group" id="list-group-res-catalog">
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">Get all readings for single sensor- Room 308 (Onservations Entity)</h6>
<!-- <small class="text-muted">STA Things Entity</small> -->
</div>
<p class="mb-1 text-muted">
https://covidsta.hft-stuttgart.de/STA-NeqModPlus/v1.1/Datastreams(24)/Observations
</p>
<a href="https://covidsta.hft-stuttgart.de/STA-NeqModPlus/v1.1/Datastreams(24)/Observations" target="_blank"><button class="btn btn-dark btn-very-sm">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-up-right-square" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M15 2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2zM0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm5.854 8.803a.5.5 0 1 1-.708-.707L9.243 6H6.475a.5.5 0 1 1 0-1h3.975a.5.5 0 0 1 .5.5v3.975a.5.5 0 1 1-1 0V6.707l-4.096 4.096z"></path>
</svg>
Try now
</button> </a>
</div>
<!-- <div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">Select where Country's name is equal to "Germany".
</h6>
<small class="text-muted">STA Things Entity + Filter</small>
</div>
<p class="mb-1 text-muted">
https://covidsta.hft-stuttgart.de/server/v1.1/Things?$filter=name eq 'Germany'
</p>
<a href="https://covidsta.hft-stuttgart.de/server/v1.1/Things?$filter=name eq 'Germany'" target="_blank"><button class="btn btn-dark btn-very-sm">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-up-right-square" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M15 2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2zM0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm5.854 8.803a.5.5 0 1 1-.708-.707L9.243 6H6.475a.5.5 0 1 1 0-1h3.975a.5.5 0 0 1 .5.5v3.975a.5.5 0 1 1-1 0V6.707l-4.096 4.096z"></path>
</svg>
Try now
</button> </a>
</div>
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">Expanding Germany Covid-19 Statistics Data</h6>
<small class="text-muted">STA Things Entity + Filter + Expand Observations</small>
</div>
<p class="mb-1 text-muted">
https://covidsta.hft-stuttgart.de/server/v1.1/Things?$filter=name eq
'Germany'&amp;$expand=Datastreams/Observations
</p>
<a href="https://covidsta.hft-stuttgart.de/server/v1.1/Things?$filter=name eq 'Germany'&amp;$expand=Datastreams/Observations" target="_blank"><button class="btn btn-dark btn-very-sm">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-up-right-square" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M15 2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2zM0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm5.854 8.803a.5.5 0 1 1-.708-.707L9.243 6H6.475a.5.5 0 1 1 0-1h3.975a.5.5 0 0 1 .5.5v3.975a.5.5 0 1 1-1 0V6.707l-4.096 4.096z"></path>
</svg>
Try now
</button> </a>
</div> -->
</div>
<hr>
<h4>Quick Demo</h4>
<div class="row">
<div class="col-md">
<!-- <iframe src="https://bw-sta.joe.in.th/grafana/d-solo/c8H9sXzGk/covid-sta?orgId=1&theme=light&panelId=3"
width="100%" height="400" frameborder="0">
</iframe> -->
<!-- <iframe src="https://data.joe.in.th/grafana/dashboard/snapshot/zjCfmj8Ktp3I12BMES5fIK6siNF7TacK" width="100%" height="400" frameborder="0"> -->
<!-- <iframe src="https://thunyatheps.grafana.net/d-solo/c8H9sXzGk/covid-sta?orgId=1&from=1640954392733&to=1648726792733&panelId=2" width="100%" height="400" frameborder="0"></iframe> -->
<!-- <iframe src="https://thunyatheps.grafana.net/d-solo/c8H9sXzGk/covid-sta?orgId=1&panelId=2" width="100%" height="400" frameborder="0"></iframe> -->
<!-- src="https://snapshot.raintank.io/dashboard-solo/snapshot/P52OVHeerdAPySF7NgAKZa3giihrz26C?orgId=2&theme=light&panelId=3" -->
<div id="container" style="height: 500px; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); position: relative;" _echarts_instance_="ec_1688035060617"><div style="position: relative; width: 1296px; height: 500px; padding: 0px; margin: 0px; border-width: 0px;"><canvas data-zr-dom-id="zr_0" width="1620" height="625" style="position: absolute; left: 0px; top: 0px; width: 1296px; height: 500px; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); padding: 0px; margin: 0px; border-width: 0px;"></canvas></div><div class=""></div></div>
</div>
</div>
<hr>
<!-- <div class="mb-3">
<h4>Important Links</h4>
<li>
<a href="https://inspire.ec.europa.eu/good-practice/ogc-sensorthings-api-inspire-download-service" target="_blank">STA Good Practice by Inspire</a>
</li>
<li>
<a href="https://doi.org/10.5194/isprs-annals-VI-4-W2-2020-135-2020" target="_blank">SensorThings API as
Data Source for COVID-19 Statistics (DOI: 10.5194/isprs-annals-VI-4-W2-2020-135-2020)</a>
</li>
<li>
<a href="http://docs.opengeospatial.org/is/15-078r6/15-078r6.html" target="_blank">SensorThings API Part
1: Sensing</a>
</li>
<li>
<a href="https://portal.ogc.org/files/?artifact_id=92752&amp;usg=AOvVaw1iiLaXXu4W6bSHCF2pJ5VW" target="_blank">OGC SensorThings API Part 1: Sensing Version 1.1</a>
</li>
<li>
<a href="https://fraunhoferiosb.github.io/FROST-Server/" target="_blank">FROST Server documentation</a>
</li>
</div> -->
<hr>
<!-- <div class="mb-3">
<h4>Contact</h4>
<b>Thunyathep Santhanavanich (JOE)</b> PhD candidate<br>
Faculty of Geomatics, Computer Science and Mathematics, <br>University of Applied Sciences Stuttgart,
Schellingstr. 24, D-70174 Stuttgart<br>
<a href="https://www.hft-stuttgart.de/p/thunyathep-santhanavanich" target="_blank">https://www.hft-stuttgart.de/p/thunyathep-santhanavanich</a>
</div> -->
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous">
</script>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5.3.1/dist/echarts.min.js"></script>
<script type="text/javascript">
var dom = document.getElementById("container");
var myChart = echarts.init(dom);
var app = {};
var option;
var dateList = []
var valueList = []
var settings = {
"url": "https://covidsta.hft-stuttgart.de/STA-NeqModPlus/v1.1/Datastreams(24)/Observations",
"method": "GET",
"timeout": 0,
};
$.ajax(settings).done(function (response) {
var timeserie_data = response.value
for (let index = 0; index < timeserie_data.length; index++) {
// element sample = {
// "resultTime": "2022-03-30T00:00:00.000Z",
// "result": 21142217
// }
var element = timeserie_data[index];
// data.push([element.resultTime, element.result])
// dateList.push(element.resultTime)
dateList = [element.resultTime, ...dateList]
// valueList.push(element.result)
valueList = [element.result, ...valueList]
if (index == timeserie_data.length - 1) {
drawchart()
}
}
});
// prettier-ignore
// const data = [["2000-06-05", 116], ["2000-06-06", 129], ["2000-06-07", 135], ["2000-06-08", 86], ["2000-06-09", 73], ["2000-06-10", 85], ["2000-06-11", 73], ["2000-06-12", 68], ["2000-06-13", 92], ["2000-06-14", 130], ["2000-06-15", 245], ["2000-06-16", 139], ["2000-06-17", 115], ["2000-06-18", 111], ["2000-06-19", 309], ["2000-06-20", 206], ["2000-06-21", 137], ["2000-06-22", 128], ["2000-06-23", 85], ["2000-06-24", 94], ["2000-06-25", 71], ["2000-06-26", 106], ["2000-06-27", 84], ["2000-06-28", 93], ["2000-06-29", 85], ["2000-06-30", 73], ["2000-07-01", 83], ["2000-07-02", 125], ["2000-07-03", 107], ["2000-07-04", 82], ["2000-07-05", 44], ["2000-07-06", 72], ["2000-07-07", 106], ["2000-07-08", 107], ["2000-07-09", 66], ["2000-07-10", 91], ["2000-07-11", 92], ["2000-07-12", 113], ["2000-07-13", 107], ["2000-07-14", 131], ["2000-07-15", 111], ["2000-07-16", 64], ["2000-07-17", 69], ["2000-07-18", 88], ["2000-07-19", 77], ["2000-07-20", 83], ["2000-07-21", 111], ["2000-07-22", 57], ["2000-07-23", 55], ["2000-07-24", 60]];
var drawchart = () => {
option = {
// Make gradient line here
visualMap: {
show: false,
type: 'continuous',
seriesIndex: 1,
dimension: 0,
min: 0,
max: dateList.length - 1
},
title: {
left: 'center',
text: 'CO2 Sensor readings'
},
tooltip: {
trigger: 'axis'
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
data: dateList,
type: 'category',
boundaryGap: false,
},
yAxis: {
type: 'value',
boundaryGap: [0, '50%']
},
series: [{
name: 'Sensor readings for Room-308',
type: 'line',
symbol: 'none',
showSymbol: false,
sampling: 'lttb',
data: valueList
}]
};
if (option && typeof option === 'object') {
myChart.setOption(option);
}
}
</script>
</body></html>
@import "./variables.scss";
@import "./mixins.scss";
/***************
Base
****************/
html,
body,
#mainViewDiv, #appDiv, #popup {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#appDiv {
top: 0;
pointer-events: none;
position: absolute;
overflow: hidden;
}
#mainViewDiv {
position: absolute;
bottom: 0;
left: 0;
@include transition(all 0.8s);
}
/***************
Typography
****************/
#appDiv {
color: $primaryColor;
font-size: $generalFontSize;
line-height: $generalFontSize + 7px;
text-shadow: 1px 1px 5px rgba(70,70,70, 1);
a {
color: $primaryColor;
text-decoration: underline;
}
h1, h2, h3, h4, h5 {
// font-family: "HelveticaNeue-CondensedBlack", "Helvetica Neue";
font-family: 'Roboto Condensed', sans-serif;
text-transform: uppercase;
font-stretch: "condensed";
pointer-events: all;
font-weight: 900;
text-shadow: 0 0 5px rgba(70,70,70, 1);
&.slash-title {
text-transform: none;
font-family: "Avenir Next";
font-stretch: "normal";
font-weight: normal;
&::before {
content: "/";
margin-right: 10px;
}
}
&.inline {
margin-right: 10px;
}
}
h2.slash-title {
margin-bottom: 50px;
margin-top: 30px;
}
h5.inline {
font-family:'Roboto Condensed', sans-serif;
font-size: 15px;
}
.inline {
display: inline;
}
p {
pointer-events: all;
}
h1 {
// font-family: "HelveticaNeue-CondensedBlack", "Helvetica Neue";
font-family:'Roboto Condensed', sans-serif;
font-stretch: "condensed";
font-weight: 900;
font-size: $bigTitleFontSize;
line-height: $bigTitleFontSize;
margin-bottom: 30px;
}
}
@media (max-width: 1400px) {
#appDiv {
font-size: 12px;
line-height: 17px;
.active .viewpoints {
margin-top: 50px;
h2.slash-title {
margin-bottom: -5px;
}
.viewpoint {
font-size: 21px;
}
}
#surroundings h1 {
font-size: 30px;
margin-bottom: 10px;
}
#surroundings .slash-title.width-toggle {
font-size: 20px;
}
#surroundings .content {
font-size: 17px;
}
#surroundings .content {
line-height: 30px;
}
#surroundings .content svg {
width: 13px;
vertical-align: -2px;
}
#surroundings .element a {
margin-left: 8px;
}
#menu {
font-size: 18px;
.slash {
margin-left: 20px;
margin-right: 15px;
}
}
.side-container {
max-width: 290px;
}
h1 {
font-size: 50px;
line-height: 50px;
}
h2.slash-title {
margin-top: 10px;
margin-bottom: 10px;
font-size: 25px;
}
.timetable {
margin-top: 20px;
.daytime {
height: 20px;
h2 {
font-size: 20px;
margin-top: -4px;
}
h3 {
font-size: 15px;
margin-bottom: 3px;
}
}
}
#surroundings .content {
margin-top: 0px;
}
.active .floor-selector .level:first-child {
margin-top: 0;
}
#floors {
h1 {
font-size: 40px;
line-height: 40px;
margin-left: 70px;
width: 220px;
&.number {
font-size: 100px;
margin-top: 20px;
margin-left: 0;
}
}
h3.subtitle {
font-size: 25px;
margin-left: 75px;
letter-spacing: 4px;
}
.level {
margin-top: 33px;
margin-left: 12px;
}
}
.side-container.left .pane {
width: 290px;
}
}
}
/***************
Layout
****************/
.side-container {
top: 0;
position: absolute;
max-width: 390px;
margin: 50px;
}
.side-container.left {
left: 0;
}
.side-container.right {
right: 0;
margin-top: 20px;
text-align: right;
}
#menu {
pointer-events: all;
text-align: center;
position: absolute;
left: 0;
position: absolute;
margin-left: auto;
margin-right: auto;
width: 100%;
text-align: center;
display: block;
font-size: $menuItemFontSize;
font-family: "Avenir Next";
font-stretch: "normal";
font-weight: medium;
top: $menuTopPosition;
cursor: pointer;
.slash {
margin-left: $menuSlashMarginLeft;
margin-right: $menuSlashMarginRight;
}
a {
text-decoration: none;
color: $menuItemColor;
cursor: pointer;
@include transition(font-size 0.3s);
&.active {
font-size: $menuItemActiveFontSize;
color: $menuItemActiveColor;
&:hover {
color: $menuItemActiveColor;
}
}
&:hover {
font-size: $menuItemOverFontSize;
color: $menuItemOverColor;
}
}
}
.esri-attribution {
display: none;
}
/***************
Mixins
****************/
@mixin transition($transition...) {
-moz-transition: $transition;
-o-transition: $transition;
-webkit-transition: $transition;
transition: $transition;
}
@mixin transition-property($property...) {
-moz-transition-property: $property;
-o-transition-property: $property;
-webkit-transition-property: $property;
transition-property: $property;
}
@mixin transition-duration($duration...) {
-moz-transition-property: $duration;
-o-transition-property: $duration;
-webkit-transition-property: $duration;
transition-property: $duration;
}
@mixin transition-timing-function($timing...) {
-moz-transition-timing-function: $timing;
-o-transition-timing-function: $timing;
-webkit-transition-timing-function: $timing;
transition-timing-function: $timing;
}
@mixin transition-delay($delay...) {
-moz-transition-delay: $delay;
-o-transition-delay: $delay;
-webkit-transition-delay: $delay;
transition-delay: $delay;
}
/***************
Colors
****************/
// Global:
$primaryColor: #fff;
$secondaryColor: #a3a3a3;
$orange: #F6A803;
// Menu:
$menuItemColor: $primaryColor;
$menuItemActiveColor: $orange;
$menuItemOverColor: $primaryColor;
// Viewpoints:
$viewpointItemColor: $primaryColor;
$viewpointItemActiveColor: $orange;
$viewpointItemOverColor: $primaryColor;
// Floors
// Surroundings
/***************
Font sizing
****************/
$generalFontSize: 15px;
$bigTitleFontSize: 5 * $generalFontSize + 10px;
// Menu
$menuItemFontSize: 23px;
$menuItemActiveFontSize: 23px;
$menuItemOverFontSize: 26px;
// Viewpoints
$viewpointItemFontSize: 25px;
$viewpointItemActiveFontSize: 40px;
$viewpointItemOverFontSize: 40px;
// Floors
$floorSelectorLevelFontSize: 25px;
$floorSelectorLevelOverFontSize: 60px;
$floorSelectorLevelActiveFontSize: 90px;
/***************
Sizing
****************/
// Menu
$menuTopPosition: 25px;
$menuSlashMarginLeft: $menuItemFontSize + 2px;
$menuSlashMarginRight: $menuItemFontSize - 3px;
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass, property } from "esri/core/accessorSupport/decorators";
import Accessor from "esri/core/Accessor";
import SceneView from "esri/views/SceneView";
import BuildingVisualisation from "./support/BuildingVisualisation";
import PopupInfo from "./widgets/Popup/PopupInfo";
@subclass("AppState")
class AppState extends Accessor {
@property()
pageLocation: string;
@property()
BldgLevel = 0;
@property()
view: SceneView;
@property()
buildingLayer: BuildingVisualisation;
@property()
popupInfo: PopupInfo;
}
export = AppState;
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass, property } from "esri/core/accessorSupport/decorators";
import { tsx } from "esri/widgets/support/widget";
// esri
import Sections from "./sections/Sections";
import SceneView from "esri/views/SceneView";
import Widget from "esri/widgets/Widget";
import * as promiseUtils from "esri/core/promiseUtils";
import Camera from "esri/Camera";
import SceneLayer from "esri/layers/SceneLayer";
import BuildingSceneLayer from "esri/layers/BuildingSceneLayer";
import WebScene from "esri/WebScene";
// BuildingViewer
import Section from "./sections/Section";
import BuildingVisualisation from "./support/BuildingVisualisation";
import SurroundingsVisualisation from "./support/SurroundingsVisualisation";
import AppState from "./AppState";
import * as appUtils from "./support/appUtils";
import Popup from "./widgets/Popup/Popup";
type SectionSublcass = Pick<Section, "camera">;
interface BuildingViewerCtorArgs {
sections: Pick<Section, "render" | "active" | "id" | "paneRight" | "title" | "camera" | "onLeave" | "onEnter" | "appState">[];
mapContainer: string;
websceneId: string;
portalUrl?: string;
floorMapping?: (originalFloor: number) => number;
extraQuery?: string;
}
@subclass("webSceneViewer.widgets.LayersLoading.LayersLoadingProgressBar")
class BuildingViewer extends Widget {
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
@property({ aliasOf: "appState.view"})
view: SceneView;
@property({ aliasOf: "sections.activeSection"})
activeSection: SectionSublcass | string | number;
@property()
sections: Sections;
@property()
appState = new AppState();
@property()
websceneId: string;
@property()
extraQuery: string;
@property()
portalUrl: string;
//--------------------------------------------------------------------------
//
// Variables:
//
//--------------------------------------------------------------------------
@property({ aliasOf: "appState.buildingLayer"})
buildingLayer: BuildingVisualisation;
@property({ aliasOf: "appState.surroundingsLayer"})
surroundingsLayer: SurroundingsVisualisation;
private firstRendering: boolean = true;
private rawSections: Pick<Section, "render" | "active" | "id" | "paneRight" | "title" | "camera" | "onLeave" | "onEnter" | "appState">[];
//--------------------------------------------------------------------------
//
// Life circle
//
//--------------------------------------------------------------------------
constructor(args: BuildingViewerCtorArgs) {
super(args as any);
this.view = appUtils.createViewFromWebScene({websceneId: args.websceneId, mapContainer: args.mapContainer, portalUrl: args.portalUrl});
if (args.floorMapping) {
this.floorMapping = args.floorMapping.bind(this);
}
}
normalizeCtorArgs(args: BuildingViewerCtorArgs) {
this.rawSections = args.sections;
delete args["sections"];
return args;
}
initialize() {
this.sections = new Sections(this.rawSections, this.appState);
(this.view.map as WebScene).when(() => {
// Save the initial layers:
promiseUtils
.eachAlways(this.view.map.layers.map((l) => this.appState.view.whenLayerView(l)))
.then(() => {
///////////////////////////////////
// Main building to present:
const BSL = this.appState.view.map.layers.find(layer => layer.title.indexOf(appUtils.MAIN_LAYER_PREFIX) > -1);
if (!BSL) {
throw new Error("Cannot find the main BuildingSceneLayer (" + appUtils.MAIN_LAYER_PREFIX + ") in the webscene " + this.websceneId);
}
const visualisationArgs: any = {
appState: this.appState,
layer: BSL as BuildingSceneLayer
};
if (this.floorMapping) {
visualisationArgs.floorMapping = this.floorMapping;
}
if (this.extraQuery) {
visualisationArgs.extraQuery = this.extraQuery;
}
this.buildingLayer = new BuildingVisualisation(visualisationArgs);
///////////////////////////////////
// Optional surrounding's layer:
const surroundingsLayer = this.appState.view.map.layers.find(layer => layer.title.toLowerCase().indexOf(appUtils.CITY_LAYER_PREFIX.toLowerCase()) > -1) as SceneLayer;
if (surroundingsLayer) {
this.surroundingsLayer = new SurroundingsVisualisation({
layer: surroundingsLayer,
appState: this.appState
});
}
});
///////////////////////////////////
// Setup camera:
this.sections.forEach((section) => {
const slide = (this.view.map as WebScene).presentation.slides.find((slide) => slide.title.text === section.title);
if (slide) {
section.camera = slide.viewpoint.camera;
(this.view.map as WebScene).presentation.slides.remove(slide);
}
else {
console.error("Could not find a slide for section " + section.title);
}
});
});
this.view.when(() => {
// Debug:
window["view"] = this.view;
window["appState"] = this.appState;
// Active first section:
if (this.sections.length > 0) {
this.sections.activateSection(this.sections.getItemAt(0).id);
}
});
this.watch("activeSection", (activeSection) => {
this.firstRendering = true;
this.renderNow();
setTimeout(() => {
this.firstRendering = false;
this.renderNow();
}, 10)
});
}
render() {
return (<div>
<div class="left side-container">{this.sections.paneLeft(this.firstRendering)}</div>
<div class="left menu">{this.sections.menu()}</div>
<div class="right side-container">{this.sections.paneRight(this.firstRendering)}</div>
</div>);
}
postInitialize() {
this.own(this.sections.on("go-to", (camera: Camera) => {
this.view.goTo(camera);
}));
new Popup({ appState: this.appState, container: "popup"});
}
floorMapping(num: number) { return num; }
}
export = BuildingViewer;
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { tsx } from "esri/widgets/support/widget";
import HomeSection from "./sections/HomeSection";
import { FloorsSection, Floor } from "./sections/FloorsSection";
// import SurroundingsSection from "./sections/SurroundingsSection";
import Collection from "esri/core/Collection";
import {Timetable, DayTimetable} from "./widgets/Timetable/Timetable";
export const portalUrl = "https://hftstuttgart.maps.arcgis.com";
//export const websceneId = "39d8c249469144faaec0aef434075788";
export const websceneId = "2f5bd708966344f99b7a6c8b6ea833ea";
export const sections = [
// Check the different files
// to adapt to your need
// or create a new section by
// implement a subclass from `Section`
// The about Turangua section:
new HomeSection({
content: (that: any) => (
<div>
<p>
The Hochschule für Technik Stuttgart holds a history of over 190 years and is one of the best universities for applied sciences in Baden-Württemberg.
The main buildings of HFT campus were established in 1819. The campus has a total of 8 buildings now, including both old and new constructions. In this project, the major focus is given to building 1 of the campus.
</p>
<div>
Note: This application is developed based on a prototype developed by ESRI. Please click <a href="https://www.esri.com/arcgis-blog/products/js-api-arcgis/3d-gis/showcase-your-bim-data-in-the-building-viewer/" target="_blank" rel="noopener noreferrer">here</a> to know more.
</div>
</div>
),
timetable: new Timetable({
dates: new Collection([
new DayTimetable({
opens: "7:00",
closes: "23:00"
}),
new DayTimetable({
opens: "7:00",
closes: "23:00"
}),
new DayTimetable({
opens: "7:00",
closes: "23:00"
}),
new DayTimetable({
opens: "7:00",
closes: "23:00"
}),
new DayTimetable({
opens: "7:00",
closes: "23:00"
}),
// new DayTimetable({
// opens: "10:00",
// closes: "17:00"
// }),
// new DayTimetable({
// opens: "10:00",
// closes: "17:00"
// })
])
})
}),
// Bau 1 of HFT Campus
// // The different floors for Turanga:
new FloorsSection({
FloorSurface: new Collection([
new Floor({
title: "Building 1",
subtitle: "Ground Floor",
// audio: "https://my.christchurchcitylibraries.com/wp-content/uploads/sites/5/2019/01/He-Hononga.mp3",
floor: 0,
content: (that: any) => (<div id="connection" bind={that} key={that}><p><span></span></p></div>)
}),
new Floor({
title: "Building 1",
subtitle: "First Floor",
// audio: "https://my.christchurchcitylibraries.com/wp-content/uploads/sites/5/2019/01/Hapori.mp3",
floor: 1,
content: (that: any) => (<div id="community" bind={that} key={that}><p><span></span></p></div>)
}),
new Floor({
title: "Building 1",
subtitle: "Second Floor",
// audio: "https://my.christchurchcitylibraries.com/wp-content/uploads/sites/5/2019/01/Tuakiri.mp3",
floor: 2,
content: (that: any) => (<div id="identity" bind={that} key={that}><p><span></span></p></div>)
}),
new Floor({
title: "Building 1",
subtitle: "Third Floor",
// audio: "https://my.christchurchcitylibraries.com/wp-content/uploads/sites/5/2019/01/T%C5%ABhuratanga.mp3",
floor: 3,
content: (that: any) => (<div id="discovery" bind={that} key={that}><p><span></span></p></div>)
}),
// new Floor({
// title: "Auahatanga",
// subtitle: "creativity",
// audio: "https://my.christchurchcitylibraries.com/wp-content/uploads/sites/5/2019/01/Auahatanga.mp3",
// floor: 4,
// content: (that: any) => (<div id="creativity" bind={that} key={that}><p><span>Browse the World Languages, Music and Fiction collections, including Biographies and Graphic Novels. Visit the two roof gardens with great views across the city. Explore your creativity in the Production Studio using creative technology such as 3D printers and sewing machines. Create and edit music and video using the Audio/Video Studio, or take a class in the Computer Labs with a great range of software available.</span></p></div>)
// })
])
}),
// Surroundings:
// new SurroundingsSection({})
];
export const floorMapping = (originalFloor: number) => {
let floor = originalFloor;
if (floor > 3) {
floor += 1;
}
return floor;
}
//export const extraQuery = " AND Category <> 'WallSurface' AND Category <> 'RoofSurface' AND Category <> 'GroundSurface'";
// export const extraQuery = " AND (Category <> 'Generic Models' OR OBJECTID_1 = 2) AND Category <> 'Walls' AND Category <> 'Roofs' AND Category <> 'Curtain Wall Mullions' AND Category <> 'Curtain Panels'";
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass, property } from "esri/core/accessorSupport/decorators";
import { tsx } from "esri/widgets/support/widget";
import Section from "./Section";
import Collection from "esri/core/Collection";
import Widget from "esri/widgets/Widget";
import FloorSelector from "../widgets/FloorSelector/FloorSelector";
import * as watchUtils from "esri/core/watchUtils";
import FeatureLayer from "esri/layers/FeatureLayer";
import Legend from "esri/widgets/Legend";
import PopupInfo from "../widgets/Popup/PopupInfo";
import * as appUtils from "../support/appUtils";
import Handles from "esri/core/Handles";
import AppState from "../AppState";
@subclass("legendWrapper")
class LegendWrapper extends Widget {
@property()
hide: boolean = true;
@property({ constructOnly: true })
appState: AppState;
@property()
legend: Legend;
constructor(args: { appState: AppState }, container: string) {
super(args as any);
}
postInitialize() {
this.legend = new Legend({
view: this.appState.view,
layerInfos: []
});
}
/*return (<div class={this.classes({"hide": this.hide})}></div> */
render() {
return (<div class={this.classes({"hide": this.hide})}>
{this.legend.render()}
</div>);
}
}
@subclass("playButton")
class PlayButton extends Widget {
@property()
playing: boolean = false;
@property()
audioSrc: string;
@property({dependsOn: ["audioSrc"], readOnly: true })
get audio() {
return new Audio(this.audioSrc);
}
postInitialize() {
this.watch("audio", audio => {
audio.addEventListener("ended", () => {
audio.currentTime = 0;
this.playing = false;
});
});
}
render() {
const dynamicCss = {
"playing": this.playing
};
return (
<button class={this.classes(dynamicCss, "play_button")} onclick={this.onClick} bind={this} key={this}>
<i class="play_button__icon">
<div class="play_button__mask"/>
</i>
</button>
);
}
onClick(event: Event) {
if (this.playing) {
this.playing = false;
this.audio.pause();
}
else {
this.audio.play();
this.playing = true;
}
}
}
interface FloorCtorArgs {
title: string;
subtitle: string;
content: (that: Floor) => any;
floor: number;
audio?: string;
}
interface FloorsSectionCtorArgs {
FloorSurface?: Collection<Floor>;
}
interface FloorsSectionCtorArgs2 {
minFloor: number;
maxFloor: number;
}
@subclass("Floor")
export class Floor extends Widget {
@property()
title: string;
@property()
content: (that: this) => any;
@property()
subtitle: string;
@property()
floor = 1;
@property({aliasOf: "playButton.audioSrc"})
audio: string;
@property()
playButton = new PlayButton();
render() {
const audio = this.audio ? (<p>Listen to the name of this floor {this.playButton.render()}</p>) : null;
return (<div>
{this.content(this)}
{audio}
</div>);
}
constructor(args: FloorCtorArgs) {
super(args as any);
}
activate() {
// put audio back to 0
this.playButton.audio.currentTime = 0;
}
}
@subclass("sections/FloorsSection")
export class FloorsSection extends Section {
@property()
title = "Floor by floor";
@property()
id = "FloorSurface";
@property({ aliasOf: "appState.BldgLevel"})
selectedFloor: number;
private oldDate: Date;
@property()
previousSelectedFloor: number;
@property()
floorSelector: FloorSelector;
@property()
legendWrapper: LegendWrapper;
@property()
layer: FeatureLayer;
@property({constructOnly: true })
layerNameForInfoPoint = appUtils.FLOOR_POINTS_LAYER_PREFIX;
@property({constructOnly: true })
layerNameForPicturePoint = appUtils.INTERNAL_INFOPOINTS_LAYER_PREFIX;
@property()
picturePointsLayer: FeatureLayer;
@property()
minFloor: number;
@property()
maxFloor: number;
private handles = new Handles();
@property({constructOnly: true})
FloorSurface: Collection<Floor>;
render() {
const currentLevel = this.FloorSurface ? this.FloorSurface.getItemAt(this.selectedFloor) : null;
// const selectedFloor = this.selectedFloor === 0 ? "G" : this.selectedFloor;
const title = currentLevel ? this.selectedFloor === 0 ? (<h1>{currentLevel.title}</h1>) : (<h1>{currentLevel.title}</h1>) : null;
return currentLevel ? (<div id={this.id} bind={this} key={this}>
<div class="level"></div>
{/* <h1 class="number">{selectedFloor}</h1> */}
{title}
<h3 class="subtitle">{currentLevel.subtitle}</h3>
<div class="content">{currentLevel.render()}</div>
</div>) : null;
}
paneRight() {
const floorSelector = this.floorSelector ? this.floorSelector.render() : null;
return (<div>{floorSelector}</div>);
}
constructor(args: FloorsSectionCtorArgs | FloorsSectionCtorArgs2) {
super(args as any);
}
postInitialize() {
watchUtils.whenOnce(this, "appState", () => {
this.legendWrapper = new LegendWrapper({
appState: this.appState
}, "floorLegend");
const floorSelectorCtorArgs = this.minFloor != null && this.maxFloor != null ? {
appState: this.appState,
minFloor: this.minFloor,
maxFloor: this.maxFloor
} : {
appState: this.appState
}
this.floorSelector = new FloorSelector(floorSelectorCtorArgs);
watchUtils.on(this, "appState.view.map.layers", "change", this.getExtraInfoLayers.bind(this));
watchUtils.init(this, "selectedFloor", (selectedFloor) => {
if (this.FloorSurface) {
this.FloorSurface.getItemAt(selectedFloor).activate();
}
// filter the picture and infoLayer:
if (this.layer) {
this.layer.definitionExpression = "level_id = " + selectedFloor;
}
if (this.picturePointsLayer) {
this.picturePointsLayer.definitionExpression = "level_id = " + selectedFloor;
}
});
});
}
onEnter() {
this.selectedFloor = 3;
if (this.FloorSurface) {
this.FloorSurface.getItemAt(this.selectedFloor).activate();
}
this.appState.view.environment.lighting.directShadowsEnabled = true;
this.appState.view.environment.lighting.ambientOcclusionEnabled = false;
this.oldDate = this.appState.view.environment.lighting.date;
this.appState.view.environment.lighting.date = new Date("Thu Aug 01 2019 13:00:00 GMT+0200 (Central European Summer Time)");
this.handles.add(this.appState.view.on("click", (event: any) => {
// the hitTest() checks to see if any graphics in the view
// intersect the given screen x, y coordinates
this.appState.view.hitTest(event)
.then((response) => {
const filtered = response.results.filter((result: any) => {
return result.graphic.layer === this.picturePointsLayer;
})[0];
if (filtered) {
this.appState.popupInfo = new PopupInfo({
image: filtered.graphic.attributes.url,
credit: filtered.graphic.attributes.title
});
}
});
}), "click");
this.legendWrapper.hide = !this.layer;
if (this.layer) {
this.layer.visible = true;
}
if (this.picturePointsLayer) {
this.picturePointsLayer.visible = true;
}
}
onLeave() {
this.handles.remove("click");
this.appState.view.environment.lighting.directShadowsEnabled = true;
this.appState.view.environment.lighting.ambientOcclusionEnabled = true;
this.appState.view.environment.lighting.date = this.oldDate;
this.legendWrapper.hide = true;
if (this.layer) {
this.layer.visible = false;
}
if (this.picturePointsLayer) {
this.picturePointsLayer.visible = false;
}
}
private getExtraInfoLayers() {
if (this.appState && this.appState.view.map.layers.length > 0) {
// Get the info points on the floors:
if (!this.layer) {
this.layer = appUtils.findLayer(this.appState.view.map.layers, this.layerNameForInfoPoint) as FeatureLayer;
if (this.layer) {
this.layer.visible = false;
this.legendWrapper.legend.layerInfos = [
{
layer: this.layer,
title: "Legend",
hideLayers: []
}
];
}
}
// Get extra pictures:
if (!this.picturePointsLayer) {
this.picturePointsLayer = appUtils.findLayer(this.appState.view.map.layers, this.layerNameForPicturePoint) as FeatureLayer;
if (this.picturePointsLayer) {
this.picturePointsLayer.visible = false;
this.picturePointsLayer.outFields = ["*"];
}
}
}
}
}
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass, property } from "esri/core/accessorSupport/decorators";
import { tsx } from "esri/widgets/support/widget";
import Section from "./Section";
import AppState from "../AppState";
import { Timetable } from "../widgets/Timetable/Timetable";
import Viewpoints from "../widgets/Viewpoints/Viewpoints";
import * as watchUtils from "esri/core/watchUtils";
import Handles from "esri/core/Handles";
import FeatureLayer from "esri/layers/FeatureLayer";
import * as appUtils from "../support/appUtils";
import Collection from "esri/core/Collection";
import PopupInfo from "../widgets/Popup/PopupInfo";
import WebScene from "esri/WebScene";
interface HomeSectionCtorArgs {
content?: (that: HomeSection) => any;
timetable?: Timetable;
title?: string;
showExternalPoints?: boolean;
}
@subclass("sections/HomeSection")
class HomeSection extends Section {
@property()
title = "Overview";
@property()
id = "home";
@property({ constructOnly: true })
timetable: Timetable;
@property()
private textTitle: string;
@property()
appState: AppState;
@property()
infoPointsLayer: FeatureLayer;
@property({ constructOnly: true })
showExternalPoints: boolean = false;
private handles = new Handles();
@property()
content: (that: this) => any = (that: this) => (this.appState.view.map as WebScene).portalItem.snippet;
@property({dependsOn: ["appState"], readOnly: true})
get viewpoints() {
return new Viewpoints({appState: this.appState});
}
render() {
const timetable = this.timetable ? (<section class="Hours">
<h2 class="slash-title">Opening hours</h2>
<div>
{this.timetable.render()}
</div>
</section>) : null;
const title = this.textTitle ? (<h1>{this.textTitle}</h1>) : null;
return (<div id={this.id}>
<div bind={this} key={this}>
{title}
{this.content(this)}
</div>
{timetable}
</div>);
}
paneRight() {
const viewpoints = this.viewpoints ? this.viewpoints.render() : null;
return (<div>{viewpoints}</div>);
}
constructor(args: HomeSectionCtorArgs) {
super(args as any);
}
postInitialize() {
// Optionally add the external info points to display pictures:
watchUtils.whenOnce(this, "appState", () => {
watchUtils.on(this, "appState.view.map.layers", "change", () => {
if (this.appState && this.appState.view.map.layers.length > 0) {
this.infoPointsLayer = this.appState.view.map.layers.find(layer => layer.title.indexOf(appUtils.EXTERNAL_INFOPOINT_LAYER_PREFIX) > -1) as FeatureLayer;
if (this.infoPointsLayer) {
this.infoPointsLayer.visible = false;
this.infoPointsLayer.outFields = ["*"];
this.infoPointsLayer.visible = false;
this.infoPointsLayer.popupTemplate.overwriteActions = true;
this.infoPointsLayer.popupTemplate.actions = new Collection();
}
}
});
});
// Get the title to display in the text:
watchUtils.whenOnce(this, "appState.view.map.portalItem.title", () => {
this.textTitle = (this.appState.view.map as WebScene).portalItem.title;
});
// Enabling external point if we are in the home section:
watchUtils.init(this, "appState.pageLocation", (l) => {
if (this.infoPointsLayer) {
this.infoPointsLayer.visible = this.showExternalPoints && l === "home";
}
});
}
onEnter() {
// reset the active viewpoint each time we go in home section:
this.viewpoints.activeViewpoint = null;
// check if we click on an external point and display a popup if that is the case:
this.handles.add(this.appState.view.on("click", (event: any) => {
this.appState.view.hitTest(event)
.then((response) => {
const filtered = response.results.filter((result: any) => {
return result.graphic.layer === this.infoPointsLayer;
})[0];
if (filtered) {
this.appState.popupInfo = new PopupInfo({
image: filtered.graphic.attributes.url,
credit: filtered.graphic.attributes.title
})
}
});
}), "click");
}
onLeave() {
// when not in home, remove the click listener:
this.handles.remove("click");
}
}
export = HomeSection;
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass,property } from "esri/core/accessorSupport/decorators";
import Camera from "esri/Camera";
import Widget from "esri/widgets/Widget";
import AppState from "../AppState";
@subclass("sections/Section")
abstract class Section extends Widget {
@property()
appState: AppState;
@property()
abstract title: string;
@property()
abstract id: string;
@property()
camera: Camera;
@property()
active: boolean = false;
abstract render(): any;
abstract paneRight(): any;
onEnter() {}
onLeave() {}
postInitialize() {
this.own(this.watch("camera", camera => {
if (camera) {
this.emit("go-to", camera);
}
}));
}
}
export = Section;
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass, property } from "esri/core/accessorSupport/decorators";
import { tsx } from "esri/widgets/support/widget";
import Collection from "esri/core/Collection";
import Section from "./Section";
import AppState from "../AppState";
type SectionSubclass = Pick<Section, "render" | "active" | "id" | "paneRight" | "title" | "camera" | "onLeave" | "onEnter" | "appState">;
@subclass("sections/Section")
class Sections extends Collection<SectionSubclass> {
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
@property()
set activeSection(sectionToActivate: SectionSubclass) {
if (sectionToActivate !== this._get("activeSection")) {
this.previousActiveSection = this.activeSection;
if (this.previousActiveSection) {
this.previousActiveSection.onLeave();
}
this.forEach(section => {
if (section !== sectionToActivate) {
section.active = false;
}
else {
section.active = true;
}
});
this.appState.pageLocation = sectionToActivate ? sectionToActivate.id : null;
this._set("activeSection", sectionToActivate);
if (this.activeSection) {
this.activeSection.onEnter();
}
}
if (this.activeSection.camera) {
this.emit("go-to", this.activeSection.camera);
}
}
@property({ constructOnly: true})
private appState: AppState;
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
previousActiveSection: SectionSubclass = null;
activeSectionNode: HTMLElement = null;
previousActiveSectionNode: HTMLElement = null;
//--------------------------------------------------------------------------
//
// Life circle
//
//--------------------------------------------------------------------------
constructor(sections: SectionSubclass[], appState: AppState) {
super(sections.map((section) => {
section.appState = appState;
return section;
}));
this.appState = appState;
this.watch("appState.pageLocation", this.activateSection);
}
//--------------------------------------------------------------------------
//
// Public methods
//
//--------------------------------------------------------------------------
activateSection(section: string | number | SectionSubclass) {
if (section instanceof Section) {
this.activeSection = section;
}
if (typeof section === "string") {
this.activeSection = this.find((s) => s.id === section);
}
if (typeof section === "number") {
this.activeSection = this.getItemAt(section);
}
}
public paneLeft(firstRendering = true) {
const panes = this.swapPanes("render", firstRendering);
return (<div id="pane-left">{panes}</div>);
}
public paneRight(firstRendering = true) {
const panes = this.swapPanes("paneRight", firstRendering);
return (<div id="pane-right">{panes}</div>);
}
public menu() {
let items = this.map((section, i) => {
const slash = i !== 0 ? (<span class="slash">/ </span>) : null;
return [slash, this.renderOneSectionMenu(section, i)];
});
return (<div id="menu">{items.toArray()}</div>);
}
private renderOneSectionMenu(section: SectionSubclass, i: number) {
const classes = section.active? "active" : "";
return (<a class={classes} href="javascript: void(0)" onclick={() => {this.activateSection(section.id);}}>{section.title}</a>);
}
private swapPanes(renderViewToCall: string, firstRendering = true) {
const activeSectionClasses = firstRendering ? "pane" : "active pane";
const previousActiveSectionClasses = firstRendering ? "active pane" : "pane";
const currentPane = this.activeSection ? (<div class={activeSectionClasses} key={this.activeSection}>{this.activeSection[renderViewToCall]()}</div>) : null;
const previousUsedPane = this.previousActiveSection ? (<div class={previousActiveSectionClasses} key={this.previousActiveSection}>{this.previousActiveSection[renderViewToCall]()}</div>) : null;
return (<div>{previousUsedPane}{currentPane}</div>);
}
}
export = Sections;
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass, property } from "esri/core/accessorSupport/decorators";
import { tsx } from "esri/widgets/support/widget";
import Section from "./Section";
import AppState from "../AppState";
import Collection from "esri/core/Collection";
import WebScene from "esri/WebScene";
import Camera from "esri/Camera";
import Widget from "esri/widgets/Widget";
import Toggle from "../widgets/Toggle/Toggle";
import * as watchUtils from "esri/core/watchUtils";
import FeatureLayer from "esri/layers/FeatureLayer";
import GroupLayer from "esri/layers/GroupLayer";
import * as appUtils from "../support/appUtils";
@subclass("SurroundingsElement")
class SurroundingsElement extends Widget {
@property()
toggle = new Toggle();
@property({aliasOf: "toggle.active"})
set active(isActive: boolean) {
this.toggle.active = isActive;
}
get active() {
return this.toggle.active;
}
@property()
title: string;
@property()
layer: FeatureLayer | GroupLayer;
@property()
appState: AppState;
@property()
camera: Camera;
activate() {
this.appState.view.goTo(this.camera);
if (this.layer) {
this.layer.visible = true;
}
}
deactivate() {
if (this.layer) {
this.layer.visible = false;
}
}
content() {
return (<div clas="content"></div>);
}
render() {
return (<div key={this} class={this.classes("element", {"active": this.active})}>
<h2 class="slash-title width-toggle" onclick={() => this.active = !this.active}>
{this.toggle.render()}
<a href="javascript:return;">{this.title}</a>
</h2>
<div clas="content">{this.content()}</div>
</div>);
}
constructor(args: any) {
super(args);
if (args.content) {
this.content = args.content.bind(this);
}
this.watch("active", (isActive) => {
if (isActive) {
this.activate();
}
else {
this.deactivate();
}
});
}
}
@subclass("PoIElement")
class PoIElement extends Widget {
@property()
camera: Camera;
@property()
name: string;
constructor(args: {name: string, camera: Camera, appState: AppState}) {
super(args as any);
}
@property()
appState: AppState;
render() {
return (
<div><span class="magnifier-icon"><svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="search" class="svg-inline--fa fa-search fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"></path></svg></span><a href="javascript: void(0)" onclick={this.onClick} bind={this} key={this}>{this.name}</a></div>
);
}
onClick(event: Event) {
this.appState.view.goTo(this.camera);
}
}
@subclass("sections/SurroundingsSection")
class SurroundingsSection extends Section {
@property()
title = "Surroundings";
@property()
appState: AppState;
@property()
id = "surroundings";
@property()
poiElements: Collection<PoIElement>;
@property({dependsOn: ["appState.view.map.layers", "poiElements"], readOnly: true})
get elements() {
if (this.appState && this.appState.view.map.layers.length > 0) {
const elements = this.appState.view.map.layers
.filter(layer => layer.title.indexOf(appUtils.SURROUNDINGS_LAYER_PREFIX) > -1)
.map(layer => {
layer.visible = false;
return new SurroundingsElement({
title: layer.title.replace("Surroundings: ", ""),
layer: layer,
appState: this.appState,
camera: this.camera
});
});
if (this.poiElements.length > 0) {
elements.push(new SurroundingsElement({
title: "Points of Interest",
appState: this.appState,
camera: this.camera,
content: () => {
const poiElementsItems = this.poiElements.map(el => el.render());
return (<div class="content">
{poiElementsItems.toArray()}
</div>);
}
}));
}
return elements;
}
else {
return new Collection<SurroundingsElement>();
}
}
constructor(args: any) {
super(args);
this.own(watchUtils.whenOnce(this, "appState", () => {
(this.appState.view.map as WebScene).when(() => {
// Get the point of interests:
this.poiElements = (this.appState.view.map as WebScene).presentation.slides
.filter(slide => slide.title.text.indexOf("Points of Interest:") > -1)
.map(slide => {
(this.appState.view.map as WebScene).presentation.slides.remove(slide);
return new PoIElement({
name: slide.title.text.replace("Points of Interest: ", ""),
camera: slide.viewpoint.camera,
appState: this.appState
});
});
watchUtils.on(this.appState, "view.map.layers", "change", () => this.notifyChange("elements"));
watchUtils.on(this, "poiElements", "change", () => this.notifyChange("elements"));
});
}));
}
render() {
return (<div id={this.id} key={this}>
<h1>Surroundings</h1>
{this.elements.map(l => l.render()).toArray()}
</div>);
}
paneRight() {
return (<div></div>);
}
onEnter() {
this.elements.forEach(el => el.active = el.title === "Points of Interest");
}
onLeave() {
this.elements.forEach(e => e.active = false);
}
}
export = SurroundingsSection;
@import "../../../css/variables.scss";
@import "../../../css/mixins.scss";
/***************
Slide animation
****************/
.side-container .pane {
@include transition(all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.6s cubic-bezier(0.42,0,0.58,1));
position: absolute;
opacity: 0;
top: 0;
}
.side-container.left .pane {
left: -1000px;
width: 390px;
}
.side-container.right .pane {
right: -1000px;
width: 190px;
}
.side-container.right .pane.active {
right: 0;
opacity: 1;
}
.side-container.left .pane.active {
left: 0;
opacity: 1;
}
.side-container.left .pane h1 {
@include transition(margin 0.7s cubic-bezier(0.68, -0.55, 0.265, 1.55));
margin-left: -200px;
}
.side-container.left .pane.active h1 {
margin-left: 0;
}
.side-container.left .pane h2 {
@include transition(margin 0.8s ease);
margin-left: -200px;
}
.side-container.left .pane h2:nth-child(2) {
@include transition(margin 0.8s ease 0.3s);
}
.side-container.left .pane h2:nth-child(3) {
@include transition(margin 0.8s ease 0.6s);
}
.side-container.left .pane.active h2 {
margin-left: 0;
}
/***************
Surroundings
****************/
#surroundings {
.element {
width: 100%;
@include transition(margin 0.3s ease);
margin-left: -200px;
a {
color: $primaryColor;
vertical-align: -2px;
margin-left: 20px;
text-decoration: none;
}
&.active {
a {
color: $orange;
}
}
}
.slash-title.width-toggle {
width: 100%;
&::before {
display: none;
}
}
// .toggle {
// border-color: $primaryColor;
// .knob {
// background-color: $primaryColor;
// }
// }
}
.active #surroundings {
.element {
margin-left: 0;
}
.element:nth-child(1) {
@include transition-delay(0.2s);
}
.element:nth-child(2) {
@include transition-delay(0.3s);
}
.element:nth-child(3) {
@include transition-delay(0.4s);
}
}
/***************
Floors
****************/
#floors {
pointer-events: all;
h1 {
margin-left: 120px;
// width:
pointer-events: all;
width: 600px;
}
h1.number {
margin-left: 0;
margin-top: 25px;
color: $orange;
}
.level, .number {
position: absolute;
}
.level {
margin-top: 61px;
z-index: 2;
margin-left: 28px;
opacity: 0.8;
}
.number {
font-size: 200px;
z-index: 1;
}
h3.subtitle {
font-family: "Avenir Next";
font-weight: normal;
font-style: italic;
font-size: 40px;
letter-spacing: 8px;
opacity: 0.3;
margin-top: -30px;
margin-left: 125px;
pointer-events: all;
}
.italic {
font-style: italic;
opacity: 0.5;
}
.content {
margin-top: 50px;
}
.play_button {
$play_button_background: scale-color($primaryColor, $lightness: -70%);
position: relative;
background: $play_button_background;
border: none;
outline: none;
padding: 6px;
border-radius: 13px;
cursor: pointer;
margin-left: 5px;
vertical-align: -2px;
border: 1px solid $primaryColor;
.play_button__icon {
$size: 12px;
height: $size;
width: $size;
line-height: $size;
position: relative;
left: 1px;
z-index: 0;
box-sizing: border-box;
display: inline-block;
overflow: hidden;
&:before, &:after {
content: '';
position: absolute;
transition: 0.3s;
background: #FFF;
height: 100%;
width: 50%;
top: 0;
}
&:before {
left: 0;
}
&:after {
right: 0;
}
}
.play_button__mask {
position: absolute;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: block;
&:before, &:after {
content: '';
position: absolute;
left: 0;
height: 100%;
width: 150%;
background: $play_button_background;
transition: all 0.3s ease-out;
}
&:before {
top: -100%;
transform-origin: 0% 100%;
transform: rotate(26.5deg);
}
&:after {
transform-origin: 0% 0%;
transform: rotate(-26.5deg);
top: 100%;
}
}
&.playing {
.play_button__icon {
left: 0;
&:before {
transform: translateX(-25%);
}
&:after {
transform: translateX(25%);
}
}
.play_button__mask {
&:before, &:after {
transform: rotate(0);
}
}
}
}
}
#floorLegend {
position: absolute;
bottom: 30px;
left: 15px;
&.hide {
display: none;
}
div {
background: transparent;
color: #fff;
font-size: 15px;
line-height: 22px;
}
.esri-legend__layer-caption, .esri-widget__heading {
display: none;
}
.esri-legend__layer-row {
height: 28px;
display: block;
}
svg {
transform: scale(0.7);
}
}
#surroundings {
.active .content {
display: block;
a {
color: #fff;
}
& > div {
a {
@include transition(all 0.8s);
}
svg {
@include transition(all 0.8s);
}
}
& > div:hover {
a {
color: #fff;
}
svg {
opacity: 1;
}
}
}
.content {
pointer-events: all;
margin-top: -30px;
font-size: 25px;
line-height: 40px;
margin-left: 25px;
display: none;
svg {
width: 15px;
opacity: 0.5;
}
}
}
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass, property } from "esri/core/accessorSupport/decorators";
// Esri
import Accessor from "esri/core/Accessor";
import BuildingSceneLayer from "esri/layers/BuildingSceneLayer";
import * as watchUtils from "esri/core/watchUtils";
import Renderer from "esri/renderers/Renderer";
import { createFilterFor, FLOOR_FILTER_NAME, definitionExpressions } from "./visualVariables";
// App
import AppState from "../AppState";
import * as buildingSceneLayerUtils from "./buildingSceneLayerUtils";
interface BuildingVisualisationCtorArgs {
layer: BuildingSceneLayer;
appState: AppState;
customBaseRenderer?: any;
floorMapping?: (originalFloor: number) => number;
extraQuery?: string;
}
@subclass("support/BuildingVisualisation")
class BuildingVisualisation extends Accessor {
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
@property()
layer: BuildingSceneLayer;
private initialRenderer: HashMap<Renderer> = {};
@property({
readOnly: true,
dependsOn: [
"appState.pageLocation",
"appState.BldgLevel"
]
})
get layerRenderer() {
return buildingSceneLayerUtils
.getVisualVarsFromAppState(
this.appState,
"mainBuilding",
"renderer"
);
}
@property()
customBaseRenderer: Renderer;
@property({
readOnly: true,
dependsOn: [
"appState.pageLocation",
"appState.BldgLevel"
]
})
get buildingFilters() {
if (this.appState.pageLocation === "FloorSurface") {
return createFilterFor(this.floorMapping(this.appState.BldgLevel), this.extraQuery);
}
return null;
}
@property({ constructOnly: true })
appState: AppState;
@property()
extraQuery: string;
//--------------------------------------------------------------------------
//
// Life circle
//
//--------------------------------------------------------------------------
constructor(args: BuildingVisualisationCtorArgs) {
super();
this.appState = args.appState;
this.layer = args.layer;
if (args.floorMapping) {
this.floorMapping = args.floorMapping;
}
if (args.extraQuery) {
this.extraQuery = args.extraQuery;
}
// Save the initial renderers, so that we can set it back:
buildingSceneLayerUtils.goThroughSubLayers(args.layer, (sublayer) => {
if (sublayer.type === "building-component") {
this.initialRenderer[sublayer.title] = (sublayer as any).renderer;
}
});
// To improve performance, we will set a definition expression that will
// force the api to load the data for floor attribute:
buildingSceneLayerUtils.goThroughSubLayers(args.layer, (sublayer) => {
if (sublayer.type === "building-component") {
sublayer.definitionExpression = definitionExpressions.basic;
}
});
watchUtils.init(this, "layerRenderer", this._updateBaseRenderer);
watchUtils.init(this, "customBaseRenderer", this._updateBaseRenderer);
// Set the building filters when necessary:
watchUtils.init(this, "buildingFilters", (buildingFilters) => {
if (!this.appState.pageLocation || this.appState.pageLocation !== "FloorSurface") {
this.layer.activeFilterId = null;
}
else {
const currentFilter = this.layer.filters.find((filter: any) => filter.name === FLOOR_FILTER_NAME);
if (currentFilter) {
this.layer.filters.remove(currentFilter);
}
this.layer.filters.push(buildingFilters);
this.layer.activeFilterId = this.layer.filters.find((filter: any) => filter.name === FLOOR_FILTER_NAME).id;
}
});
}
normalizeCtorArgs(args: BuildingVisualisationCtorArgs) {
return {
appState: args.appState
};
}
//--------------------------------------------------------------------------
//
// Private Methods
//
//--------------------------------------------------------------------------
private _updateBaseRenderer() {
if (this.customBaseRenderer) {
buildingSceneLayerUtils.updateSubLayers(this.layer, ["renderer"], this.customBaseRenderer);
}
else if (!this.appState.pageLocation || this.appState.pageLocation === "home" || this.appState.pageLocation === "custom") {
buildingSceneLayerUtils.goThroughSubLayers(this.layer, (sublayer) => {
if (sublayer.type === "building-component") {
sublayer.renderer = this.initialRenderer[sublayer.title] && (this.initialRenderer[sublayer.title] as any).clone();
}
});
}
else {
buildingSceneLayerUtils.updateSubLayers(this.layer, ["renderer"], this.layerRenderer);
}
}
private floorMapping(originalFloor: number) {
return originalFloor;
}
}
export = BuildingVisualisation;
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass, property } from "esri/core/accessorSupport/decorators";
import Accessor from "esri/core/Accessor";
// App
import AppState from "../AppState";
import * as buildingSceneLayerUtils from "./buildingSceneLayerUtils";
import * as watchUtils from "esri/core/watchUtils";
import Renderer from "esri/renderers/Renderer";
import SceneLayer from "esri/layers/SceneLayer";
@subclass("support/SurroundingsVisualisation")
class SurroundingsVisualisation extends Accessor {
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
@property({
readOnly: true,
dependsOn: [
"appState.pageLocation",
"appState.BldgLevel"
]
})
get surroundingsRenderer() {
return buildingSceneLayerUtils
.getVisualVarsFromAppState(
this.appState,
"surroundings",
"renderer"
);
}
@property()
customRenderer: Renderer;
@property({
readOnly: true,
dependsOn: [
"appState.pageLocation",
"appState.BldgLevel"
]
})
get surroundingsOpacity() {
return buildingSceneLayerUtils
.getVisualVarsFromAppState(
this.appState,
"surroundings",
"opacity"
);
}
@property()
layer: SceneLayer;
@property({ constructOnly: true})
appState: AppState;
//--------------------------------------------------------------------------
//
// Life circle
//
//--------------------------------------------------------------------------
constructor(args: {layer: SceneLayer, appState: AppState}) {
super();
this.appState = args.appState;
this.layer = args.layer;
this.layer.when(() => {
watchUtils.init(this, "surroundingsRenderer", this._updateBaseRenderer);
watchUtils.init(this, "customRenderer", this._updateBaseRenderer);
watchUtils.init(this, "surroundingsOpacity", (surroundingsOpacity) => {
this.layer.opacity = surroundingsOpacity;
});
});
}
//--------------------------------------------------------------------------
//
// Private Methods
//
//--------------------------------------------------------------------------
private _updateBaseRenderer() {
if (this.customRenderer) {
this.layer.renderer = this.customRenderer;
}
else {
this.layer.renderer = this.surroundingsRenderer;
}
}
}
export = SurroundingsVisualisation;
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import WebScene from "esri/WebScene";
import SceneView from "esri/views/SceneView";
import Layer from "esri/layers/Layer";
import Collection from "esri/core/Collection";
import PortalItem from "esri/portal/PortalItem";
import Portal from "esri/portal/Portal";
export function createViewFromWebScene(args: {
mapContainer: string,
websceneId: string,
portalUrl?: string
}) {
const portalItem = new PortalItem({
id: args.websceneId
});
// Let user add portal parameter
if (args.portalUrl) {
portalItem.portal = new Portal({
url: args.portalUrl
});
}
// Load webscene and display it in a SceneView
const webscene = new WebScene({
portalItem
});
const view = new SceneView({
container: args.mapContainer,
map: webscene
});
view.when(() => {
view.padding = { left: 300 };
view.popup.autoOpenEnabled = true;
});
// Remove default ui:
view.ui.empty("top-left");
view.ui.empty("bottom-left");
return view;
}
export function findLayer(layers: Collection<Layer>, title: string) {
return layers.find(l => l.title === title);
}
export const CITY_LAYER_PREFIX = "City model";
export const MAIN_LAYER_PREFIX = "Building";
export const FLOOR_POINTS_LAYER_PREFIX = "Floor points";
export const INTERNAL_INFOPOINTS_LAYER_PREFIX = "Floor pictures";
export const EXTERNAL_INFOPOINT_LAYER_PREFIX = "External pictures";
export const SURROUNDINGS_LAYER_PREFIX = "Surroundings:";
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import BuildingSceneLayer from "esri/layers/BuildingSceneLayer";
import BuildingComponentSublayer from "esri/layers/buildingSublayers/BuildingComponentSublayer";
import BuildingGroupSublayer from "esri/layers/buildingSublayers/BuildingGroupSublayer";
import { renderers } from "./visualVariables";
import AppState from "../AppState";
export function updateSubLayersSymbolLayer (buildingLayer: BuildingSceneLayer, propertyPath: string[], value: any) {
buildingLayer.when(function() {
buildingLayer.allSublayers.forEach(function(layer) {
if (layer instanceof BuildingComponentSublayer && (layer.renderer as any).clone) {
const renderer = (layer.renderer as any).clone();
let parentProp = renderer.symbol.symbolLayers.getItemAt(0);
propertyPath.forEach(function (prop, i) {
if (i === (propertyPath.length - 1)) {
parentProp[prop] = value;
}
else {
parentProp = parentProp[prop];
}
});
layer.renderer = renderer;
}
});
});
}
export function updateSubLayers(buildingLayer: BuildingSceneLayer, propertyPath: string[], value: any) {
buildingLayer.when(function() {
buildingLayer.allSublayers.forEach(function(layer) {
let parentProp = layer;
propertyPath.forEach(function (prop, i) {
if (i === (propertyPath.length - 1)) {
parentProp[prop] = value;
}
else {
parentProp = parentProp[prop];
}
});
});
});
}
export function goThroughSubLayers(buildingLayer: BuildingSceneLayer, callback: (sublayers: BuildingComponentSublayer | BuildingGroupSublayer) => void) {
buildingLayer.when(function() {
buildingLayer.allSublayers.forEach(function(layer) {
callback(layer);
});
});
}
export function getVisualVarsFromAppState(appState: AppState, layerName: string, propertyName: string) {
const defaultProps = renderers[layerName]["default"][propertyName];
const customPage = renderers[layerName][appState.pageLocation] ? renderers[layerName][appState.pageLocation][propertyName] : undefined;
if (customPage !== undefined) {
return customPage;
}
return defaultProps;
}
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import SimpleRenderer from "esri/renderers/SimpleRenderer";
export const renderers = {
surroundings: {
//--------------------------------------------------------------------------
//
// Surroundings
//
//--------------------------------------------------------------------------
// This is used when displaying the different pages and
// when there is no other variables defined
default: {
renderer: {
type: "simple",
symbol: {
type: "mesh-3d",
symbolLayers: [{
type: "fill",
material: { color: [100,100,100, 1], colorMixMode: "replace" },
edges: {
type: "solid", // autocasts as new SolidEdges3D()
color: [30, 30, 30, 1]
}
}]
}
},
// Opacity when displaying the different pages and
opacity: 1
},
"surroundings": {
renderer: {
type: "simple",
symbol: {
type: "mesh-3d",
// castShadows: false,
symbolLayers: [{
type: "fill",
material: { color: [255,255,255, 1], colorMixMode: "tint" },
edges: {
type: "solid", // autocasts as new SolidEdges3D()
color: [30, 30, 30, 1]
}
}]
}
} as any
},
"FloorSurface": {
opacity: 1
}
},
mainBuilding: {
//--------------------------------------------------------------------------
//
// Building
//
//--------------------------------------------------------------------------
// This is used when displaying the different pages and
// when there is no other variables defined
default: {
renderer: new SimpleRenderer({
symbol: {
type: "mesh-3d",
symbolLayers: [{
type: "fill",
material: { color: [255,184,1, 1], colorMixMode: "replace" },
edges: {
type: "solid", // autocasts as new SolidEdges3D()
color: [0, 0, 0, 1]
}
}]
}
} as any),
// Opacity when displaying the different pages and
opacity: 1
},
// This is used when displaying the different floors:
"FloorSurface": {
renderer: new SimpleRenderer({
symbol: {
type: "mesh-3d",
symbolLayers: [{
type: "fill",
material: { color: [255,255,255, 1], colorMixMode: "replace" },
edges: {
type: "solid", // autocasts as new SolidEdges3D()
color: [30, 30, 30, 1]
}
}]
}
} as any)
},
"surroundings": {
renderer: null as any
}
}
};
// Some useful definitionExpression:
export const definitionExpressions = {
basic: "BldgLevel IS NULL OR BldgLevel IS NOT NULL",
// this is used to filter FeatureLayer:
floor: function (BldgLevel: number, extraQuery = "") {
return "BldgLevel = " + BldgLevel + extraQuery;
}
};
export const FLOOR_FILTER_NAME = "FloorSurface";
export function createFilterFor(BldgLevel: number, extraQuery?: string) /*: BuildingFilter*/ {
return {
filterBlocks: [
{
filterMode: { type: "solid" },
filterExpression: definitionExpressions.floor(BldgLevel, extraQuery),
title: "floor"
}
],
name: FLOOR_FILTER_NAME
};
}
/*
* Copyright 2019 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { subclass, property } from "esri/core/accessorSupport/decorators";
import { tsx } from "esri/widgets/support/widget";
import Widget from "esri/widgets/Widget";
import AppState from "../../AppState";
interface FloorSelectorCtorArgs {
minFloor: number;
maxFloor: number;
appState: AppState;
}
interface FloorSelectorCtorArgs2 {
appState: AppState;
}
@subclass("widgets/FloorSelector")
class FloorSelector extends Widget {
@property({aliasOf: "appState.BldgLevel"})
activeFloor: number;
@property()
maxFloor = 3;
@property()
minFloor = 0;
@property({constructOnly: true})
appState: AppState;
render() {
const levels = Array.from(Array(Math.abs(this.minFloor) + this.maxFloor + 1).keys()).reverse().map((rawLevel: number) => {
const level: number = rawLevel - this.minFloor;
const levelText = level === 0 ? "G" : level;
const activeClass = {
"active": this.activeFloor === level
};
return (<li class={this.classes("level", activeClass)} onclick={() => this.activeLevel(level)}>{levelText}</li>);
});
return (<div bind={this} key={this} class="floor-selector">
<h2 class="slash-title">Select floor</h2>
<ul>{levels}</ul>
</div>);
}
private activeLevel(newLevel: number) {
event.stopPropagation();
this.activeFloor = newLevel;
}
constructor(args: FloorSelectorCtorArgs | FloorSelectorCtorArgs2) {
super(args as any);
}
}
export = FloorSelector;
@use "sass:math";
@import "../../../../css/variables.scss";
@import "../../../../css/mixins.scss";
$transition-time: 0.5s;
.floor-selector {
pointer-events: all;
.level {
font-family: 'Roboto Condensed', sans-serif; // import font!
cursor: pointer;
list-style: none;
display: block;
pointer-events: all;
width: 100%;
font-size: $floorSelectorLevelFontSize;
text-align: right;
@include transition(all $transition-time);
margin-left: 200px;
&:hover {
font-size: $floorSelectorLevelOverFontSize;
}
&:first-child {
margin-top: -$floorSelectorLevelFontSize - 15px;
}
&.active {
font-size: $floorSelectorLevelActiveFontSize;
margin-top: math.div(-$floorSelectorLevelActiveFontSize, 2) + 15px;
margin-bottom: math.div(-$floorSelectorLevelActiveFontSize, 2) + 15px;
color: #F6A803;
text-transform: uppercase;
pointer-events: all;
&:first-child {
margin-top: -$floorSelectorLevelActiveFontSize + 10px;
}
}
}
}
.active .floor-selector {
.level {
margin-left: 0;
&.active {
margin-left: 15px;
}
}
.level:nth-child(1) {
@include transition(all $transition-time, margin-left 0.8s 0.1s);
}
.level:nth-child(2) {
@include transition(all $transition-time, margin-left 0.8s 0.2s);
}
.level:nth-child(3) {
@include transition(all $transition-time, margin-left 0.8s 0.3s);
}
.level:nth-child(4) {
@include transition(all $transition-time, margin-left 0.8s 0.4s);
}
.level:nth-child(5) {
@include transition(all $transition-time, margin-left 0.8s 0.5s);
}
.level:nth-child(6) {
@include transition(all $transition-time, margin-left 0.8s 0.6s);
}
.level:nth-child(7) {
@include transition(all $transition-time, margin-left 0.8s 0.7s);
}
.level:nth-child(8) {
@include transition(all $transition-time, margin-left 0.8s 0.8s);
}
.level:nth-child(9) {
@include transition(all $transition-time, margin-left 0.8s 0.9s);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment