-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
3,178 additions
and
3,202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,16 @@ | ||
FROM node:22-alpine as build | ||
|
||
WORKDIR /app | ||
COPY . . | ||
|
||
RUN npm install --ignore-scripts | ||
RUN npm run build --omit=dev | ||
|
||
|
||
|
||
FROM nginx:alpine | ||
|
||
WORKDIR /usr/share/nginx/html | ||
COPY --from=build /app/dist/ /usr/share/nginx/html | ||
|
||
EXPOSE 80 | ||
|
||
CMD ["nginx", "-g", "daemon off;"] | ||
FROM node:22-alpine AS build | ||
|
||
WORKDIR /app | ||
COPY . . | ||
|
||
RUN npm install --ignore-scripts && npm run build --omit=dev | ||
|
||
|
||
FROM nginx:alpine | ||
|
||
WORKDIR /usr/share/nginx/html | ||
COPY --from=build /app/dist/ /usr/share/nginx/html | ||
|
||
EXPOSE 80 | ||
|
||
CMD ["nginx", "-g", "daemon off;"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,99 +1,97 @@ | ||
import { | ||
Cartographic, | ||
Cartesian3, | ||
CustomDataSource, | ||
HeadingPitchRange, | ||
Math as CMath, | ||
BoundingSphere, | ||
Rectangle, | ||
|
||
} from 'cesium'; | ||
import {EARTHQUAKE_SPHERE_SIZE_COEF, getColorFromTime, parseEarthquakeData} from './helpers'; | ||
import {LayerType} from '../constants'; | ||
|
||
export default class EarthquakeVisualizer { | ||
/** | ||
* @param {import('cesium/Source/Widgets/Viewer/Viewer').default} viewer | ||
* @param {Object} config | ||
*/ | ||
constructor(viewer, config) { | ||
this.viewer = viewer; | ||
this.config = config; | ||
this.earthquakeDataSource = new CustomDataSource(LayerType.earthquakes); | ||
this.viewer.dataSources.add(this.earthquakeDataSource); | ||
this.boundingSphere = null; | ||
this.boundingRectangle = new Rectangle( | ||
Number.POSITIVE_INFINITY, | ||
Number.POSITIVE_INFINITY, | ||
Number.NEGATIVE_INFINITY, | ||
Number.NEGATIVE_INFINITY | ||
); | ||
this.maximumHeight = 0; | ||
this.earthquakeDataSource.entities.collectionChanged.addEventListener(() => { | ||
this.viewer.scene.requestRender(); | ||
}); | ||
} | ||
|
||
async showEarthquakes() { | ||
const response = await fetch(this.config.downloadUrl); | ||
const text = await response.text(); | ||
parseEarthquakeData(text).map(data => { | ||
const size = Number(data.Magnitude.split(' ')[0]) * EARTHQUAKE_SPHERE_SIZE_COEF; | ||
const depthMeters = Number(data.Depthkm.split(' ')[0]) * 1000; // convert km to m | ||
const longitude = Number(data.Longitude); | ||
const latitude = Number(data.Latitude); | ||
delete data.Longitude; | ||
delete data.Latitude; | ||
const position = Cartesian3.fromDegrees(longitude, latitude, -depthMeters); | ||
const posCart = Cartographic.fromCartesian(position); | ||
const altitude = this.viewer.scene.globe.getHeight(posCart) || 0; | ||
posCart.height = posCart.height + altitude; | ||
Cartographic.toCartesian(posCart, undefined, position); | ||
const cameraDistance = size * 4; | ||
const zoomHeadingPitchRange = new HeadingPitchRange(0, CMath.toRadians(25), cameraDistance); | ||
data['Details'] = this.config.detailsUrl; | ||
this.boundingRectangle.west = Math.min(CMath.toRadians(longitude), this.boundingRectangle.west); | ||
this.boundingRectangle.south = Math.min(CMath.toRadians(latitude), this.boundingRectangle.south); | ||
this.boundingRectangle.east = Math.max(CMath.toRadians(longitude), this.boundingRectangle.east); | ||
this.boundingRectangle.north = Math.max(CMath.toRadians(latitude), this.boundingRectangle.north); | ||
this.maximumHeight = Math.max(this.maximumHeight, depthMeters * 2); | ||
return this.earthquakeDataSource.entities.add({ | ||
position: position, | ||
ellipsoid: { | ||
radii: new Cartesian3(size, size, size), | ||
material: getColorFromTime(data.Time), | ||
}, | ||
properties: { | ||
...data, | ||
propsOrder: this.config.propsOrder, | ||
zoomHeadingPitchRange: zoomHeadingPitchRange | ||
} | ||
}); | ||
}); | ||
this.boundingSphere = BoundingSphere.fromRectangle3D(this.boundingRectangle); | ||
} | ||
|
||
/** | ||
* @param {boolean} visible | ||
*/ | ||
async setVisible(visible) { | ||
const entities = this.earthquakeDataSource.entities.values; | ||
if (entities && entities.length) { | ||
this.earthquakeDataSource.show = visible; | ||
} else { | ||
if (visible) { | ||
await this.showEarthquakes(); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param {number} opacity | ||
*/ | ||
setOpacity(opacity) { | ||
const entities = this.earthquakeDataSource.entities.values; | ||
entities.forEach(entity => { | ||
entity.ellipsoid.material = entity.ellipsoid.material.color.getValue().withAlpha(Number(opacity)); | ||
}); | ||
} | ||
} | ||
import { | ||
Cartographic, | ||
Cartesian3, | ||
CustomDataSource, | ||
HeadingPitchRange, | ||
Math as CMath, | ||
BoundingSphere, | ||
Rectangle, | ||
|
||
} from 'cesium'; | ||
import {EARTHQUAKE_SPHERE_SIZE_COEF, getColorFromTime, parseEarthquakeData} from './helpers'; | ||
import {LayerType} from '../constants'; | ||
|
||
export default class EarthquakeVisualizer { | ||
/** | ||
* @param {import('cesium/Source/Widgets/Viewer/Viewer').default} viewer | ||
* @param {Object} config | ||
*/ | ||
constructor(viewer, config) { | ||
this.viewer = viewer; | ||
this.config = config; | ||
this.earthquakeDataSource = new CustomDataSource(LayerType.earthquakes); | ||
this.viewer.dataSources.add(this.earthquakeDataSource); | ||
this.boundingSphere = null; | ||
this.boundingRectangle = new Rectangle( | ||
Number.POSITIVE_INFINITY, | ||
Number.POSITIVE_INFINITY, | ||
Number.NEGATIVE_INFINITY, | ||
Number.NEGATIVE_INFINITY | ||
); | ||
this.maximumHeight = 0; | ||
this.earthquakeDataSource.entities.collectionChanged.addEventListener(() => { | ||
this.viewer.scene.requestRender(); | ||
}); | ||
} | ||
|
||
async showEarthquakes() { | ||
const response = await fetch(this.config.downloadUrl); | ||
const text = await response.text(); | ||
parseEarthquakeData(text).map(data => { | ||
const size = Number(data.Magnitude.split(' ')[0]) * EARTHQUAKE_SPHERE_SIZE_COEF; | ||
const depthMeters = Number(data.Depthkm.split(' ')[0]) * 1000; // convert km to m | ||
const longitude = Number(data.Longitude); | ||
const latitude = Number(data.Latitude); | ||
delete data.Longitude; | ||
delete data.Latitude; | ||
const position = Cartesian3.fromDegrees(longitude, latitude, -depthMeters); | ||
const posCart = Cartographic.fromCartesian(position); | ||
const altitude = this.viewer.scene.globe.getHeight(posCart) || 0; | ||
posCart.height = posCart.height + altitude; | ||
Cartographic.toCartesian(posCart, undefined, position); | ||
const cameraDistance = size * 4; | ||
const zoomHeadingPitchRange = new HeadingPitchRange(0, CMath.toRadians(25), cameraDistance); | ||
data['Details'] = this.config.detailsUrl; | ||
this.boundingRectangle.west = Math.min(CMath.toRadians(longitude), this.boundingRectangle.west); | ||
this.boundingRectangle.south = Math.min(CMath.toRadians(latitude), this.boundingRectangle.south); | ||
this.boundingRectangle.east = Math.max(CMath.toRadians(longitude), this.boundingRectangle.east); | ||
this.boundingRectangle.north = Math.max(CMath.toRadians(latitude), this.boundingRectangle.north); | ||
this.maximumHeight = Math.max(this.maximumHeight, depthMeters * 2); | ||
return this.earthquakeDataSource.entities.add({ | ||
position: position, | ||
ellipsoid: { | ||
radii: new Cartesian3(size, size, size), | ||
material: getColorFromTime(data.Time), | ||
}, | ||
properties: { | ||
...data, | ||
propsOrder: this.config.propsOrder, | ||
zoomHeadingPitchRange: zoomHeadingPitchRange | ||
} | ||
}); | ||
}); | ||
this.boundingSphere = BoundingSphere.fromRectangle3D(this.boundingRectangle); | ||
} | ||
|
||
/** | ||
* @param {boolean} visible | ||
*/ | ||
async setVisible(visible) { | ||
const entities = this.earthquakeDataSource.entities.values; | ||
if (entities && entities.length) { | ||
this.earthquakeDataSource.show = visible; | ||
} else if (visible) { | ||
await this.showEarthquakes(); | ||
} | ||
} | ||
|
||
/** | ||
* @param {number} opacity | ||
*/ | ||
setOpacity(opacity) { | ||
const entities = this.earthquakeDataSource.entities.values; | ||
entities.forEach(entity => { | ||
entity.ellipsoid.material = entity.ellipsoid.material.color.getValue().withAlpha(Number(opacity)); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,64 @@ | ||
import {html, PropertyValues} from 'lit'; | ||
import {until} from 'lit/directives/until.js'; | ||
import {customElement, state} from 'lit/decorators.js'; | ||
import draggable from './draggable'; | ||
import i18next from 'i18next'; | ||
import {LitElementI18n} from '../i18n.js'; | ||
|
||
import {LayerConfig, LayerType} from '../layertree'; | ||
import {unsafeHTML} from 'lit/directives/unsafe-html.js'; | ||
import {classMap} from 'lit/directives/class-map.js'; | ||
import {dragArea} from './helperElements'; | ||
|
||
@customElement('ngm-layer-legend') | ||
export class NgmLayerLegend extends LitElementI18n { | ||
@state() | ||
accessor config!: LayerConfig; | ||
|
||
protected firstUpdated(_changedProperties: PropertyValues) { | ||
// hidden is required to have correct window placing | ||
this.hidden = true; | ||
draggable(this, { | ||
allowFrom: '.drag-handle' | ||
}); | ||
this.hidden = false; | ||
super.firstUpdated(_changedProperties); | ||
} | ||
|
||
getImageLegend() { | ||
const legendImage = this.config.legend ? `https://api.geo.admin.ch/static/images/legends/${this.config.legend}_${i18next.language}.png` : undefined; | ||
return html`${legendImage ? html` | ||
<div class="ngm-legend-container"> | ||
<div>${i18next.t('dtd_legend')}</div> | ||
<div class="ngm-legend-image"><img src="${legendImage}"></div> | ||
</div> | ||
` : ''}`; | ||
} | ||
|
||
async getWmtsLegend() { | ||
const response = await fetch(`https://api3.geo.admin.ch/rest/services/api/MapServer/${this.config.layer}/legend?lang=${i18next.language}`); | ||
const legendHtml = await response.text(); | ||
return html` | ||
<div class="ngm-legend-html"> | ||
${unsafeHTML(legendHtml)} | ||
</div>`; | ||
} | ||
|
||
render() { | ||
return html` | ||
<div class="ngm-floating-window-header drag-handle"> | ||
${i18next.t(this.config.label)} | ||
<div class="ngm-close-icon" @click=${() => this.dispatchEvent(new CustomEvent('close'))}></div> | ||
</div> | ||
<div class="content-container ${classMap({'legend-html': this.config.type === LayerType.swisstopoWMTS})}"> | ||
${this.config.type === LayerType.swisstopoWMTS ? html`${until(this.getWmtsLegend(), html` | ||
<div class="ui loader"></div>`)}` : this.getImageLegend()} | ||
</div> | ||
${dragArea} | ||
`; | ||
} | ||
|
||
createRenderRoot() { | ||
// no shadow dom | ||
return this; | ||
} | ||
} | ||
import {html, PropertyValues} from 'lit'; | ||
import {until} from 'lit/directives/until.js'; | ||
import {customElement, state} from 'lit/decorators.js'; | ||
import draggable from './draggable'; | ||
import i18next from 'i18next'; | ||
import {LitElementI18n} from '../i18n.js'; | ||
|
||
import {LayerConfig, LayerType} from '../layertree'; | ||
import {unsafeHTML} from 'lit/directives/unsafe-html.js'; | ||
import {classMap} from 'lit/directives/class-map.js'; | ||
import {dragArea} from './helperElements'; | ||
|
||
@customElement('ngm-layer-legend') | ||
export class NgmLayerLegend extends LitElementI18n { | ||
@state() | ||
accessor config!: LayerConfig; | ||
|
||
protected firstUpdated(_changedProperties: PropertyValues) { | ||
// hidden is required to have correct window placing | ||
this.hidden = true; | ||
draggable(this, { | ||
allowFrom: '.drag-handle' | ||
}); | ||
this.hidden = false; | ||
super.firstUpdated(_changedProperties); | ||
} | ||
|
||
getImageLegend() { | ||
const legendImage = this.config.legend ? `https://api.geo.admin.ch/static/images/legends/${this.config.legend}_${i18next.language}.png` : undefined; | ||
return legendImage && html` | ||
<div class="ngm-legend-container"> | ||
<div>${i18next.t('dtd_legend')}</div> | ||
<div class="ngm-legend-image"><img src="${legendImage}"></div> | ||
</div>`; | ||
} | ||
|
||
async getWmtsLegend() { | ||
const response = await fetch(`https://api3.geo.admin.ch/rest/services/api/MapServer/${this.config.layer}/legend?lang=${i18next.language}`); | ||
const legendHtml = await response.text(); | ||
return html` | ||
<div class="ngm-legend-html"> | ||
${unsafeHTML(legendHtml)} | ||
</div>`; | ||
} | ||
|
||
render() { | ||
return html` | ||
<div class="ngm-floating-window-header drag-handle"> | ||
${i18next.t(this.config.label)} | ||
<div class="ngm-close-icon" @click=${() => this.dispatchEvent(new CustomEvent('close'))}></div> | ||
</div> | ||
<div class="content-container ${classMap({'legend-html': this.config.type === LayerType.swisstopoWMTS})}"> | ||
${this.config.type === LayerType.swisstopoWMTS ? until(this.getWmtsLegend(), html` | ||
<div class="ui loader"></div>`) : this.getImageLegend()} | ||
</div> | ||
${dragArea} | ||
`; | ||
} | ||
|
||
createRenderRoot() { | ||
// no shadow dom | ||
return this; | ||
} | ||
} |
Oops, something went wrong.