diff --git a/assetActions.config.js b/assetActions.config.js index 4896380c0..555ed2a43 100644 --- a/assetActions.config.js +++ b/assetActions.config.js @@ -1,9 +1,20 @@ +//import Felt from './src/actions/assets/Felt.js'; +import Cesium from './src/actions/assets/Cesium.js'; import CopcViewer from './src/actions/assets/CopcViewer.js'; +import F3D from './src/actions/assets/F3D.js'; import GeoJsonIo from './src/actions/assets/GeoJsonIo.js'; -//import Felt from './src/actions/assets/Felt.js'; +// import Geofox from './src/actions/assets/Geofox.js'; +// import Geoparquet from './src/actions/assets/Geoparquet.js'; +// import Potree from './src/actions/assets/Potree.js'; +import Protomaps from './src/actions/assets/Protomaps.js'; export default { + //Felt + Cesium, CopcViewer, + F3D, GeoJsonIo, -//Felt + // Geoparquet, // not ready yet + // Potree, + Protomaps, }; \ No newline at end of file diff --git a/docs/actions.md b/docs/actions.md index 12138addd..75d4969d9 100644 --- a/docs/actions.md +++ b/docs/actions.md @@ -47,6 +47,44 @@ import GeoJsonIo from './src/actions/assets/GeoJsonIo.js'; export default { GeoJsonIo }; ``` +### OGC3dTiles + +Adds an `Open in Geofox.ai` button that allows to open OGC 3D Tiles files on or Cesium Sandcastle. + +```js +import OGC3dTiles from './src/actions/assets/OGC3dTiles.js'; +export default { OGC3dTiles }; +``` + +### geoparquet.info + +Adds an `Open in geoparquet.info` button that allows to open GeoParquet files on . + +```js +import Geoparquet from './src/actions/assets/Geoparquet.js'; +export default { Geoparquet }; +``` + +### potree.org + +Adds an `Open in potree.org` button that allows to open COPC and Potree files on (via [Darren Wiens](https://mpc-copc-viewer.netlify.app) or [Iconem](https://3d.iconem.com/tools/load_potree_project_from_urlparam.html) apps) + +```js +import Potree from './src/actions/assets/Potree.js'; +export default { Potree }; +``` + +### pmtiles.io + +Adds an `Open in pmtiles.io` button that allows to open Protomaps PMTiles files on . + +```js +import Protomaps from './src/actions/assets/Protomaps.js'; +export default { Protomaps }; +``` + + + ## Links All actions for links are stored in the folder [`src/actions/links`](../src/actions/links). @@ -61,4 +99,22 @@ The link to the XYZ has to follow the [web-map-links extension](https://github.c ```js import Felt from './src/actions/links/Felt.js'; export default { Felt }; +``` + +### pmtiles.io + +Adds an `Open in pmtiles.io` button that allows to open Protomaps PMTiles files on . + +```js +import Protomaps from './src/actions/assets/Protomaps.js'; +export default { Protomaps }; +``` + +### OGC3dTiles + +Adds an `Open in Geofox.ai` button that allows to open OGC 3D Tiles files on or Cesium Sandcastle. + +```js +import OGC3dTiles from './src/actions/assets/OGC3dTiles.js'; +export default { OGC3dTiles }; ``` \ No newline at end of file diff --git a/linkActions.config.js b/linkActions.config.js index 04e566c4b..44ed63380 100644 --- a/linkActions.config.js +++ b/linkActions.config.js @@ -1,5 +1,10 @@ -//import Felt from './src/actions/links/Felt.js'; +// import Felt from './src/actions/links/Felt.js'; +// import Geofox from './src/actions/links/Geofox.js'; +import Cesium from './src/actions/links/Cesium.js'; +import Protomaps from './src/actions/links/Protomaps.js'; export default { -//Felt + // Felt, + Cesium, + Protomaps }; \ No newline at end of file diff --git a/src/actions/assets/Cesium.js b/src/actions/assets/Cesium.js new file mode 100644 index 000000000..467b9be43 --- /dev/null +++ b/src/actions/assets/Cesium.js @@ -0,0 +1,50 @@ +import AssetActionPlugin from "../AssetActionPlugin"; +import URI from 'urijs'; +import i18n from "../../i18n"; + +// See mime types discussion for 3d-tiles here and there +// https://github.com/opengeospatial/ogcapi-3d-geovolumes/issues/13 +const OGC3DTILES_SUPPORTED_TYPES = [ + // 'application/json', + 'application/3dtiles+json', +]; + +export default class Cesium extends AssetActionPlugin { + + get show() { + return this.component.isBrowserProtocol && OGC3DTILES_SUPPORTED_TYPES.includes(this.asset.type); + } + + get uri() { + // https://sandcastle.cesium.com/standalone.html vs https://sandcastle.cesium.com/index.html + let uri = new URI("https://sandcastle.cesium.com/standalone.html"); + const tileset_url = this.component.href; + const code_payload = { + html: ` + +
+ `, + code: ` + const viewer = new Cesium.Viewer("cesiumContainer", { + terrain: Cesium.Terrain.fromWorldTerrain(), + }); + + try { + const tileset = await Cesium.Cesium3DTileset.fromUrl('${tileset_url}'); + viewer.scene.primitives.add(tileset); + viewer.zoomTo(tileset); + } catch (error) { + console.log('Error loading tileset'); + } + `.replaceAll(' ', '') + }; + const code_str = btoa(JSON.stringify(code_payload)); + uri.addQuery('code', code_str); + return uri; + } + + get text() { + return i18n.t('actions.openIn', {service: 'Cesium Sandcastle'}); + } + +} \ No newline at end of file diff --git a/src/actions/assets/CopcViewer.js b/src/actions/assets/CopcViewer.js index 0159929cd..7c8ff8922 100644 --- a/src/actions/assets/CopcViewer.js +++ b/src/actions/assets/CopcViewer.js @@ -5,7 +5,10 @@ import i18n from "../../i18n"; export default class CopcViewer extends AssetActionPlugin { get show() { - return this.component.isBrowserProtocol && this.asset.type === 'application/vnd.laszip+copc'; + return this.component.isBrowserProtocol && ( + this.asset.type === 'application/vnd.laszip+copc' + || URI(this.asset.href).filename() == 'ept.json' + ); } get uri() { diff --git a/src/actions/assets/F3D.js b/src/actions/assets/F3D.js new file mode 100644 index 000000000..d4d56e5b9 --- /dev/null +++ b/src/actions/assets/F3D.js @@ -0,0 +1,45 @@ +import AssetActionPlugin from "../AssetActionPlugin"; +import URI from 'urijs'; +import i18n from "../../i18n"; + +// obj & ply files are usually with mime-type text/plain +const F3D_SUPPORTED_TYPES = [ + 'model/gltf-binary', + 'model/gltf+json', + 'application/fbx', +]; +// below is usually text/plain +const F3D_SUPPORTED_FILEEXTS = ['obj', 'ply', 'fbx', 'glb', 'gltf']; +export default class F3D extends AssetActionPlugin { + + get show() { + const suffix = URI(this.asset.href).suffix(); + return this.component.isBrowserProtocol && ( + F3D_SUPPORTED_TYPES.includes(this.asset.type) + || F3D_SUPPORTED_FILEEXTS.some(ext => (suffix === ext)) + ); + } + + get uri() { + // `https://f3d.app/web/#model=${modelUrl}` see PR merged for parsing model url and extension: https://github.com/f3d-app/f3d/pull/1596 + // Could enforce extension to help f3d.app determine the mesh type and loader to use + let uri = new URI("https://f3d.app/web"); + uri.addQuery("model", this.component.href); + uri = uri.toString().replace('?', '#'); + return uri; + } + + get uri_3dviewer() { + // `https://3dviewer.net/#model=${modelUrl}` misconception, # is not a fragment but a query, can be replaced + // let uri = new URI("https://3dviewer.net/"); + // uri.addQuery("model", this.component.href); + // uri = uri.toString().replace('?', '#'); + let uri = `https://3dviewer.net/#model=${this.component.href.replace('%2F', '/')}`; + return uri; + } + + get text() { + return i18n.t('actions.openIn', {service: 'f3d.app'}); + } + +} \ No newline at end of file diff --git a/src/actions/assets/Geofox.js b/src/actions/assets/Geofox.js new file mode 100644 index 000000000..79aa59abb --- /dev/null +++ b/src/actions/assets/Geofox.js @@ -0,0 +1,28 @@ +import AssetActionPlugin from "../AssetActionPlugin"; +import URI from 'urijs'; +import i18n from "../../i18n"; + +// See mime types discussion for 3d-tiles here and there +// https://github.com/opengeospatial/ogcapi-3d-geovolumes/issues/13 +const OGC3DTILES_SUPPORTED_TYPES = [ + // 'application/json', + 'application/3dtiles+json', +]; + +export default class Geofox extends AssetActionPlugin { + + get show() { + return this.component.isBrowserProtocol && OGC3DTILES_SUPPORTED_TYPES.includes(this.asset.type); + } + + get uri() { + let uri = new URI("https://viewer.geofox.ai/"); + uri.addQuery('tileset', this.component.href); + return uri; + } + + get text() { + return i18n.t('actions.openIn', {service: 'Geofox.ai'}); + } + +} \ No newline at end of file diff --git a/src/actions/assets/Geoparquet.js b/src/actions/assets/Geoparquet.js new file mode 100644 index 000000000..a6930f4cf --- /dev/null +++ b/src/actions/assets/Geoparquet.js @@ -0,0 +1,27 @@ +import AssetActionPlugin from "../AssetActionPlugin"; +import URI from 'urijs'; +import i18n from "../../i18n"; + +const GEOPARQUET_SUPPORTED_TYPES = [ + 'application/vnd.apache.parquet', + 'application/x-parquet' +]; + +export default class Geoparquet extends AssetActionPlugin { + + get show() { + return this.component.isBrowserProtocol && GEOPARQUET_SUPPORTED_TYPES.includes(this.asset.type); + } + + get uri() { + let uri = new URI("https://geoparquet.info"); + uri.addQuery('url', this.component.href); + return uri; + } + + + get text() { + return i18n.t('actions.openIn', {service: 'Geoparquet.info'}); + } + +} \ No newline at end of file diff --git a/src/actions/assets/Potree.js b/src/actions/assets/Potree.js new file mode 100644 index 000000000..08a489a56 --- /dev/null +++ b/src/actions/assets/Potree.js @@ -0,0 +1,48 @@ +import AssetActionPlugin from "../AssetActionPlugin"; +import URI from 'urijs'; +import i18n from "../../i18n"; + +const POTREE_SUPPORTED_TYPES = [ + 'application/vnd.laszip+copc', +]; + +// this.component.filename.endsWith or this.asset.href.includes +const POTREE_SUPPORTED_FILEEXTS = [ + 'cloud.js', 'metadata.json', 'ept.json' + // potree v1, potree v2, EPT Entwine Point Tiles +]; + +export default class Potree extends AssetActionPlugin { + + get show() { + // todo: this should check for the pointcloud extension or other indications that this asset is actually point cloud related + // as the metadata.json matching is rather greedy. Should be possible once stac-js is implemented. + return this.component.isBrowserProtocol && ( + POTREE_SUPPORTED_TYPES.includes(this.asset.type) + || POTREE_SUPPORTED_FILEEXTS.map( + f => URI(this.asset.href).filename().endsWith(f) + ).some(e => e) + ); + } + + get uri() { + // Docs: PR made to original potree repo to avoid relying on iconem own infrastructure + // https://github.com/potree/potree/pull/1456 + // would be accessible via https://potree.org/potree/examples/load_potree_project_from_urlparam.html + // Can also parse pointSize, FOV, opacity, edlEnabled, edlRadius, edlStrength, pointBudget, showBoundingBox, pointSizing, quality, position, target, background via loadSettingsFromURL + // Alternatives with single potree-supported tileset support and less param parsed + // https://mpc-copc-viewer.netlify.app?c=rgba&r= Darren Wiens app, which works eg with IGN COPC: + // https://potree.org/potree/examples/copc.html?c=rgba&r= Potree copc app + let uri = new URI("https://3d.iconem.com/apps/load_potree_project_from_urlparam"); + const datasetUrl = this.component.href; + uri.addQuery('fit', 'true'); + uri.addQuery('c', 'elevation'); // rgba, elevation, intensity etc + uri.addQuery('datasetsUrls', `["${datasetUrl}"]`); + return uri; + } + + get text() { + return i18n.t('actions.openIn', {service: 'potree.org'}); + } + +} \ No newline at end of file diff --git a/src/actions/assets/Protomaps.js b/src/actions/assets/Protomaps.js new file mode 100644 index 000000000..23227ccf8 --- /dev/null +++ b/src/actions/assets/Protomaps.js @@ -0,0 +1,30 @@ +import AssetActionPlugin from "../AssetActionPlugin"; +import URI from 'urijs'; +import i18n from "../../i18n"; + +// obj & ply files are usually with mime-type text/plain +const PROTOMAPS_SUPPORTED_TYPES = [ + 'application/vnd.pmtiles', +]; + +export default class Protomaps extends AssetActionPlugin { + + get show() { + // Rather check if .pmtiles substring present in this.asset.href or simply this.component.filename.endsWith('pmtiles') + return this.component.isBrowserProtocol && ( + PROTOMAPS_SUPPORTED_TYPES.includes(this.asset.type) + || URI(this.asset.href).suffix() == 'pmtiles' + ); + } + + get uri() { + let uri = new URI("https://pmtiles.io/"); + uri.addQuery("url", this.component.href); // returns the URI instance for chaining + return uri; + } + + get text() { + return i18n.t('actions.openIn', {service: 'Protomaps'}); + } + +} \ No newline at end of file diff --git a/src/actions/links/Cesium.js b/src/actions/links/Cesium.js new file mode 100644 index 000000000..32d76f899 --- /dev/null +++ b/src/actions/links/Cesium.js @@ -0,0 +1,43 @@ +import LinkActionPlugin from "../LinkActionPlugin"; +import URI from 'urijs'; +import i18n from "../../i18n"; + +export default class Cesium extends LinkActionPlugin { + + get show() { + return this.link.rel === '3d-tiles'; + } + + get uri() { + // https://sandcastle.cesium.com/standalone.html vs https://sandcastle.cesium.com/index.html + let uri = new URI("https://sandcastle.cesium.com/standalone.html"); + const tileset_url = this.link.href; + const code_payload = { + html: ` + +
+ `, + code: ` + const viewer = new Cesium.Viewer("cesiumContainer", { + terrain: Cesium.Terrain.fromWorldTerrain(), + }); + + try { + const tileset = await Cesium.Cesium3DTileset.fromUrl('${tileset_url}'); + viewer.scene.primitives.add(tileset); + viewer.zoomTo(tileset); + } catch (error) { + console.log('Error loading tileset'); + } + `.replaceAll(' ', '') + }; + const code_str = btoa(JSON.stringify(code_payload)); + uri.addQuery('code', code_str); + return uri; + } + + get text() { + return i18n.t('actions.openIn', {service: 'Cesium Sandcastle'}); + } + +} \ No newline at end of file diff --git a/src/actions/links/Geofox.js b/src/actions/links/Geofox.js new file mode 100644 index 000000000..c93bd0607 --- /dev/null +++ b/src/actions/links/Geofox.js @@ -0,0 +1,21 @@ +import LinkActionPlugin from "../LinkActionPlugin"; +import URI from 'urijs'; +import i18n from "../../i18n"; + +export default class Geofox extends LinkActionPlugin { + + get show() { + return this.link.rel === '3d-tiles'; + } + + get uri() { + let uri = new URI("https://viewer.geofox.ai/"); + uri.addQuery('tileset', this.link.href); + return uri; + } + + get text() { + return i18n.t('actions.openIn', {service: 'Geofox.ai'}); + } + +} \ No newline at end of file diff --git a/src/actions/links/Protomaps.js b/src/actions/links/Protomaps.js new file mode 100644 index 000000000..1ac790402 --- /dev/null +++ b/src/actions/links/Protomaps.js @@ -0,0 +1,22 @@ +import LinkActionPlugin from "../LinkActionPlugin"; +import URI from 'urijs'; +import i18n from "../../i18n"; + +export default class Protomaps extends LinkActionPlugin { + + get show() { + return this.link.rel === 'pmtiles'; + // could also check if this.link.type === "application/vnd.pmtiles" + } + + get uri() { + let uri = new URI("https://pmtiles.io/"); + uri.addQuery("url", this.link.href); + return uri; + } + + get text() { + return i18n.t('actions.openIn', {service: 'Protomaps'}); + } + +} \ No newline at end of file