Loading Local Non-Hosted Tileset

I am writing an app in ReactJS/Typescript hosted in Electron. The goal is to generate a cross-platform app that is as close to a ‘desktop’ app as possible. We are technically using Resium, which is a wrapper package around Cesium for React. We have things working fairly well, but I am wanting to work with Tilesets to display lidar data. For kml’s, I was able to manually load and parse the file in my main process, then pass that to the renderer process to make into a GeoJson object which can then be loaded into Cesium. Using the GoCesiumTiler cmd line tool, I have been able to generate a Tileset from a .las file, but is there any way to get that into my Cesium Viewer without having to host it? I.e. like a manual data parsing work around like what I did for Kml’s?

Maybe I’m missing some context. But a common way to make tilesets available locally (on one machine, without uploading them somewhere) is to host them with a local server, and access them via a URL like http://localhost:8003/tileset.json. There are different ways for setting up such a local server, one of them is described at 3d-tiles-samples/INSTRUCTIONS.md at main · CesiumGS/3d-tiles-samples · GitHub

(An aside: I the context of an Electron-based application, I also once tried the approach of “intercepting” the web requests that are sent out from the browser that runs in Electron, and then returning data that was loaded from a local file. I think that this is technically possible, but … requires some work, and may still be quirky in some ways…)

Thanks for the reply Marco. It’s hard to explain, but what I was getting at is there any way to go from a local file path basically to a Cesium3dTileset object. No servers or anything in between. I understand this is a little out of the norm, but again, trying for a standalone desktop app. For KmlSource and GeoJson, they can accept a ‘data’ parameter, so I am able to read in a file path, build an object, and pass it to the Cesium component. It looks like with a Tileset it only accepts a Url, no data object.

Yes Electron is a fun beast to work with at times…

I’m about 99% sure (and would have to try it out and read some code to arrive at 100%) that the tileset URL can also be a 'data: URL. So you could just read the data locally by whatever means, and then pass it to the tileset creation function as a data URL.
(You’ll still have to think about how to resolve the references from the tileset. I.e. when the tileset refers to some content.glb file, you’ll have to resolve that somehow…). But … maybe starting at the top-level, a data URL could be a viable approach…?

It is sounding like that might be the most complicated way to do things… I am just nervous for setting up a server due to lack of experience and potential complications when trying to ship that with a desktop app.

For clarification, if I use the http server route it sounds like i’ll have to run my las-to-pnts cmd line tool and then serve it so that I can fetch it (adding quite a few steps) ?

Should I be looking at Cesium Ion Self Hosted? It sounds like you have some prior knowledge of Electron. Knowing that I am using the React-Electron-Boilerplate and Resium (instead of javascript native cesiumJs), what is the recommended method of viewing lidar data? Other than to upload it to my cesium ion account?

It is sounding like that might be the most complicated way to do things…

There may be simpler ways of doing this. Some details may require a better understanding of the context/setup of the application.

It sounds like you have some prior knowledge of Electron.

Not really. I just did some experiments a while ago. This is not related to my actual work for Cesium, but only a spare-time project. But the goal was to have a stanadlone Cesium application with the possibility to drag-and-drop local tileset files into the viewer:

SemiCesium Standalone Loading

(Ignore the general appearance and that :slight_smile: - these are just experiments, and I’m not a frontend guy…)

So when your Electron application is running locally, and just wants to access a tileset that is stored on the same machine, then there might be the option to just use the file: protocol for the URL.

A relevant snippet that generated the console messages shown in that screencap is this:

import NodeUrl from "url";
...

      const filePath = files[0].path;
      console.log("Handle drop of " + filePath);
      const nodeUrl = NodeUrl.pathToFileURL(filePath);
      const url = nodeUrl.toString();

      const tileset = await Cesium3DTileset.fromUrl(url);
      console.log("Created tileset from drop of " + filePath);
      console.log("which was converted to URL   " + url);

So the actual file path
C:\Example\BatchedColors\tileset.json
is just converted into the URL
file:///C:/Example/BatchedColors/tileset.json
and passed to the tileset.

Whether or not this works for you … is hard to say. Electron is a beast. For example, since this was only a local experiment, I simply enabled nodeIntegration and disabled contextIsolation, as in

    const browserWindow = new BrowserWindow({
      webPreferences: {
        nodeIntegration: true,
        contextIsolation: false,
        ...
      },
      ...
    });

These might otherwise prevent accessing local file URLs (but that’s just a guess - I’d have to refresh my memory and confirm some details here).

Hi Marco,

I think what you and I are/were trying to do is fairly similar. An attempt to create a Cesium standalone application. Just in my case, I am attempting to get a lidar point cloud instead of geometry shapes in your example. I have tried to implement what you have in my ‘main’ process (the electron file, not my renderer file). My code:

try {
  const filePath = arrayOfFiles[0]?.filePath;
  console.log(`Handle drop of ${filePath}`);
  const nodeUrl = NodeUrl.pathToFileURL(filePath);
  const url = nodeUrl.toString();

  const tileset = await Cesium3DTileset.fromUrl(url);
  console.log(`Created tileset from drop of ${filePath}`);
  console.log(`which was converted to URL   ${url}`);
  newFileArray.push(tileset);
  console.log('TILESET: ', tileset);
} catch (error) {
  console.error('Could not create tileset...', error);
}

The console response I get is as follows (and less than helpful haha)…

open tile dialog triggered
[ ‘C:\out\tileset.json’ ]
load tileset triggered, ARGS: [ { parentId: ‘’, type: ‘’, filePath: ‘C:\out\tileset.json’ } ]
Handle drop of C:\out\tileset.json
Could not create tileset… RequestErrorEvent {
statusCode: undefined,
response: undefined,
responseHeaders: undefined
}

Note that the “geometry shapes” that are shown initially are just Primitive objects that I aded to the viewer for testing.
The “boxes” that you see after dropping are indeed the contents of the tileset.json that was dropped, and these contents (B3DM files) are properly resolved, based on the tileset JSON file URL.
This is just to point out: If the data is a tileset that can be loaded with CesiumJS, then the type of the content (whether it’s point clouds (PNTS) or geometry (B3DM)) should not matter.

Regarding the error … I could only make guesses for now. Printing the URL before creating the tileset might give a hint whether there’s something wrong with the url, but … that seems unlikely. A vague guess is that it might be related to some security restriction. Diving deeper into that may involve the usual steps (beyond comparing OS/Electron/Cesium versions and such), e.g. looking at the stack trace there so see whether that might give a hint about the actual reason for the error.

(Interestingly, quickly websearching for electron file url "RequestErrorEvent" leads to cesium/CHANGES.md at main · CesiumGS/cesium · GitHub which mentions a change that involves a RequestErrorEvent in Cesium 1.114 (and I did my tests with 1.112), but … that might be a red herring…)

Hi Marco,

I was wondering if you might have had any further thoughts on this local loading? I’ve also been in contact with Cesium Sales, but that hasn’t been quite as fruitful. They just instruct me to set up Express or some type of server, and if at all possible I’m trying to avoid that. By getting the formatted ‘RequestErrorEvent’ as stated above, I feel that I am close because I think Cesium actually creates that error object if I’m not mistaken. I’ve printed paths, url’s, etc and can’t really find an issue. I’ve also read in the json data from the tileset myself, but it doesn’t seem that there is any way to directly go from a data object (or already read in json) to a Cesium Tileset. Any thoughts?

I couldn’t reproduce that RequestErrorEvent until now.
(But again, that “standalone CesiumJS/Electron app” that I created back then was rather an experiment, casually stumbling through a error-google-stackoverflow cycle - so I might have avoided this error “by accident” at some point…).

I just updated its CesiumJS dependency to 1.119 and tried it, but it stil worked, so that change that appeared in 1.114 does not seem to make the difference.

There are very few places where such a RequestErrorEvent is raised. And since your console output contained that statusCode: undefined… part, I think that it boils down to

I.e. places where the constructor is called without parameters.
(An aside: I think that this should not be done. I mean … even just a console.log("Something wrong!", e) could be more helpful there…).

However, maybe setting a breakpoint or debugstepping to the try... part of these catches brings some insight.

It’s only a wild guess, but I still think that this might be related to some security issue. (At least, most of the things that I stumbled over had been in that category. I mean, … when nothing works, then … that’s the ultimate form of ‘security’, isn’t it?).

I assume that when you are creating the BrowserWindow, then you are not setting

    const browserWindow = new BrowserWindow({
      webPreferences: {
        nodeIntegration: true,
        contextIsolation: false,
        preload: path.resolve(path.join(__dirname, "preload.js")),
      },
      icon: path.join(__dirname, "favicon.png"),
    });

is that correct?
(I think that contextIsolation: false part would hardly be done in a ‘real’ application. The problem is that I’d have to try and set up something without that setting, which requires quite a few structural changes for proper IPC and such…)