From 81d8ee9b480a0491352e719e2b4d1f579e00d51c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Br=C3=A9dif?= Date: Wed, 11 Apr 2018 12:51:20 +0200 Subject: [PATCH 1/7] feat(parsers) adds a parser registry to the scheduler --- src/Core/Scheduler/Scheduler.js | 41 ++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Core/Scheduler/Scheduler.js b/src/Core/Scheduler/Scheduler.js index 3b48479927..08e226620a 100644 --- a/src/Core/Scheduler/Scheduler.js +++ b/src/Core/Scheduler/Scheduler.js @@ -5,6 +5,8 @@ */ import PriorityQueue from 'js-priority-queue'; +import CancelledCommandException from './CancelledCommandException'; + import WMTSProvider from '../../Provider/WMTSProvider'; import WMSProvider from '../../Provider/WMSProvider'; import TileProvider from '../../Provider/TileProvider'; @@ -14,7 +16,16 @@ import PointCloudProvider from '../../Provider/PointCloudProvider'; import WFSProvider from '../../Provider/WFSProvider'; import RasterProvider from '../../Provider/RasterProvider'; import StaticProvider from '../../Provider/StaticProvider'; -import CancelledCommandException from './CancelledCommandException'; + +import GpxParser from '../../Parser/GpxParser'; +import XbilParser from '../../Parser/XbilParser'; +import GeoJsonParser from '../../Parser/GeoJsonParser'; +import PntsParser from '../../Parser/PntsParser'; +import B3dmParser from '../../Parser/B3dmParser'; +import PotreeBinParser from '../../Parser/PotreeBinParser'; +import PotreeCinParser from '../../Parser/PotreeCinParser'; +// import GLTFLoader from '../../Parser/GLTFLoader'; +// import LegacyGLTFLoader from '../../Parser/LegacyGLTFLoader'; var instanceScheduler = null; @@ -80,12 +91,14 @@ function Scheduler() { this.hostQueues = new Map(); this.providers = {}; + this.parsers = {}; this.maxConcurrentCommands = 16; this.maxCommandsPerHost = 6; // TODO: add an options to not instanciate default providers this.initDefaultProviders(); + this.initDefaultParsers(); } Scheduler.prototype.constructor = Scheduler; @@ -105,6 +118,20 @@ Scheduler.prototype.initDefaultProviders = function initDefaultProviders() { this.addProtocolProvider('static', StaticProvider); }; +Scheduler.prototype.initDefaultParsers = function initDefaultParsers() { + // Register all parsers + this.addFormatParser('gpx', GpxParser); + this.addFormatParser('xbil', XbilParser); + this.addFormatParser('application/json', GeoJsonParser); + this.addFormatParser('geojson', GeoJsonParser); + this.addFormatParser('pnts', PntsParser); + this.addFormatParser('b3dm', B3dmParser); + this.addFormatParser('bin', PotreeBinParser); + this.addFormatParser('cin', PotreeCinParser); + // this.addFormatParser('gltf', GLTFLoader); + // this.addFormatParser('gltf-legacy', LegacyGLTFLoader); +}; + Scheduler.prototype.runCommand = function runCommand(command, queue, executingCounterUpToDate) { var provider = this.providers[command.layer.protocol]; @@ -256,6 +283,18 @@ Scheduler.prototype.getProtocolProvider = function getProtocolProvider(protocol) return this.providers[protocol]; }; + +Scheduler.prototype.addFormatParser = function addParser(format, parser) { + if (typeof (parser.parse) !== 'function') { + throw new Error(`Can't add parser for ${format}: missing a parse function.`); + } + this.parsers[format] = parser; +}; + +Scheduler.prototype.getFormatParser = function getFormatParser(format) { + return this.parsers[format]; +}; + Scheduler.prototype.commandsWaitingExecutionCount = function commandsWaitingExecutionCount() { let sum = this.defaultQueue.storage.length + this.defaultQueue.counters.executing; for (var q of this.hostQueues) { From 3780a4e7100a31680e6de1043164594cd3257d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Br=C3=A9dif?= Date: Thu, 12 Apr 2018 15:15:11 +0200 Subject: [PATCH 2/7] feat(providers): potree provider usarsers and providers --- src/Core/Scheduler/Scheduler.js | 12 ++++-------- src/Parser/PotreeBinParser.js | 4 ++-- src/Parser/PotreeCinParser.js | 4 ++-- src/Provider/PointCloudProvider.js | 29 +++++++++++++---------------- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/Core/Scheduler/Scheduler.js b/src/Core/Scheduler/Scheduler.js index 08e226620a..d7d9328605 100644 --- a/src/Core/Scheduler/Scheduler.js +++ b/src/Core/Scheduler/Scheduler.js @@ -18,14 +18,9 @@ import RasterProvider from '../../Provider/RasterProvider'; import StaticProvider from '../../Provider/StaticProvider'; import GpxParser from '../../Parser/GpxParser'; -import XbilParser from '../../Parser/XbilParser'; import GeoJsonParser from '../../Parser/GeoJsonParser'; -import PntsParser from '../../Parser/PntsParser'; -import B3dmParser from '../../Parser/B3dmParser'; import PotreeBinParser from '../../Parser/PotreeBinParser'; import PotreeCinParser from '../../Parser/PotreeCinParser'; -// import GLTFLoader from '../../Parser/GLTFLoader'; -// import LegacyGLTFLoader from '../../Parser/LegacyGLTFLoader'; var instanceScheduler = null; @@ -121,11 +116,12 @@ Scheduler.prototype.initDefaultProviders = function initDefaultProviders() { Scheduler.prototype.initDefaultParsers = function initDefaultParsers() { // Register all parsers this.addFormatParser('gpx', GpxParser); - this.addFormatParser('xbil', XbilParser); + // this.addFormatParser('xbil', XbilParser); this.addFormatParser('application/json', GeoJsonParser); this.addFormatParser('geojson', GeoJsonParser); - this.addFormatParser('pnts', PntsParser); - this.addFormatParser('b3dm', B3dmParser); + this.addFormatParser('json', GeoJsonParser); + // this.addFormatParser('pnts', PntsParser); + // this.addFormatParser('b3dm', B3dmParser); this.addFormatParser('bin', PotreeBinParser); this.addFormatParser('cin', PotreeCinParser); // this.addFormatParser('gltf', GLTFLoader); diff --git a/src/Parser/PotreeBinParser.js b/src/Parser/PotreeBinParser.js index f9493e5adc..620c176ded 100644 --- a/src/Parser/PotreeBinParser.js +++ b/src/Parser/PotreeBinParser.js @@ -7,7 +7,7 @@ export default { /** Parse .bin PotreeConverter format and convert to THREE.Points * @function parse * @param {ArrayBuffer} buffer - the bin buffer. - * @return {Promise} a promise that resolves with a THREE.Points. + * @return {Promise} a promise that resolves as { object3d: }. * */ parse: function parse(buffer) { @@ -57,6 +57,6 @@ export default { points.realPointCount = numPoints; points.tightbbox = tightbbox; - return Promise.resolve(points); + return Promise.resolve({ object3d: points }); }, }; diff --git a/src/Parser/PotreeCinParser.js b/src/Parser/PotreeCinParser.js index a5dc11b78f..ed44ee2318 100644 --- a/src/Parser/PotreeCinParser.js +++ b/src/Parser/PotreeCinParser.js @@ -6,7 +6,7 @@ export default { /** Parse .cin PotreeConverter format (see {@link https://github.com/peppsac/PotreeConverter/tree/custom_bin}) and convert to THREE.Points * @function parse * @param {ArrayBuffer} buffer - the cin buffer. - * @return {Promise} - a promise that resolves with a THREE.Points. + * @return {Promise} - a promise that resolves as { object3d: }. * */ parse: function parse(buffer) { @@ -37,6 +37,6 @@ export default { points.realPointCount = numPoints; points.tightbbox = tightbbox; - return Promise.resolve(points); + return Promise.resolve({ object3d: points }); }, }; diff --git a/src/Provider/PointCloudProvider.js b/src/Provider/PointCloudProvider.js index f88c0400aa..8a85599f00 100644 --- a/src/Provider/PointCloudProvider.js +++ b/src/Provider/PointCloudProvider.js @@ -1,8 +1,6 @@ import * as THREE from 'three'; import Fetcher from './Fetcher'; import PointCloudProcessing from '../Process/PointCloudProcessing'; -import PotreeBinParser from '../Parser/PotreeBinParser'; -import PotreeCinParser from '../Parser/PotreeCinParser'; import Picking from '../Core/Picking'; // Create an A(xis)A(ligned)B(ounding)B(ox) for the child `childIndex` of one aabb. @@ -109,6 +107,10 @@ function findChildrenByName(node, name) { let nextuuid = 1; function addPickingAttribute(points) { + if (!points) { + console.warn('Pointcloud tile failed to parse to an object3d'); + return undefined; + } // generate unique id for picking const numPoints = points.geometry.attributes.position.count; const ids = new Uint8Array(4 * numPoints); @@ -132,19 +134,15 @@ function addPickingAttribute(points) { return points; } - function loadPointFile(layer, url) { - return fetch(url, layer.fetchOptions).then(foo => foo.arrayBuffer()).then((ab) => { - if (layer.metadata.customBinFormat) { - return PotreeCinParser.parse(ab).then(result => addPickingAttribute(result)); - } else { - return PotreeBinParser.parse(ab).then(result => addPickingAttribute(result)); - } - }); + return fetch(url, layer.fetchOptions) + .then(dat => dat.arrayBuffer()) + .then(buf => layer.parser.parse(buf)) + .then(obj => addPickingAttribute(obj.object3d)); } export default { - preprocessDataLayer(layer) { + preprocessDataLayer(layer, view, scheduler) { if (!layer.file) { layer.file = 'cloud.js'; } @@ -187,7 +185,8 @@ export default { // (if `scale` is defined => we're fetching files from PotreeConverter) if (layer.metadata.scale != undefined) { // PotreeConverter format - layer.metadata.customBinFormat = layer.metadata.pointAttributes === 'CIN'; + layer.format = layer.format || (layer.metadata.pointAttributes === 'CIN' ? 'cin' : 'bin'); + layer.parser = scheduler.getFormatParser(layer.format); bbox = new THREE.Box3( new THREE.Vector3(cloud.boundingBox.lx, cloud.boundingBox.ly, cloud.boundingBox.lz), new THREE.Vector3(cloud.boundingBox.ux, cloud.boundingBox.uy, cloud.boundingBox.uz)); @@ -196,7 +195,7 @@ export default { layer.metadata.scale = 1; layer.metadata.octreeDir = `itowns/${layer.table}.points`; layer.metadata.hierarchyStepSize = 1000000; // ignore this with lopocs - layer.metadata.customBinFormat = true; + layer.format = layer.format || 'cin'; let idx = 0; for (const entry of cloud) { @@ -234,13 +233,11 @@ export default { parseOctree(layer, layer.metadata.hierarchyStepSize, node).then(() => command.view.notifyChange(false)); } - const extension = layer.metadata.customBinFormat ? 'cin' : 'bin'; - // `isLeaf` is for lopocs and allows the pointcloud server to consider that the current // node is the last one, even if we could subdivide even further. // It's necessary because lopocs doens't know about the hierarchy (it generates it on the fly // when we request .hrc files) - const url = `${node.baseurl}/r${node.name}.${extension}?isleaf=${command.isLeaf ? 1 : 0}`; + const url = `${node.baseurl}/r${node.name}.${layer.format}?isleaf=${command.isLeaf ? 1 : 0}`; return loadPointFile(layer, url).then((points) => { points.position.copy(node.bbox.min); From 13bdb68b984c11af53ebce2e210189a01cfbe381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Br=C3=A9dif?= Date: Thu, 12 Apr 2018 15:22:57 +0200 Subject: [PATCH 3/7] refactor(parsers): GpxParser output normalization --- examples/gpx.html | 4 ++-- src/Core/Scheduler/Scheduler.js | 5 ----- src/Parser/GpxParser.js | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/examples/gpx.html b/examples/gpx.html index b1977cd0b1..d48d5f6c82 100644 --- a/examples/gpx.html +++ b/examples/gpx.html @@ -39,8 +39,8 @@ globeView.addEventListener(itowns.GLOBE_VIEW_EVENTS.GLOBE_INITIALIZED, function () { console.info('Globe initialized'); itowns.Fetcher.xml('https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/ULTRA2009.gpx').then(xml => itowns.GpxParser.parse(xml, { crs: globeView.referenceCrs })).then(function (gpx) { - if (gpx) { - globeView.scene.add(gpx); + if (gpx && gpx.object3d) { + globeView.scene.add(gpx.object3d); globeView.controls.setTilt(45, true); } }); diff --git a/src/Core/Scheduler/Scheduler.js b/src/Core/Scheduler/Scheduler.js index d7d9328605..b9ee1e3e00 100644 --- a/src/Core/Scheduler/Scheduler.js +++ b/src/Core/Scheduler/Scheduler.js @@ -116,16 +116,11 @@ Scheduler.prototype.initDefaultProviders = function initDefaultProviders() { Scheduler.prototype.initDefaultParsers = function initDefaultParsers() { // Register all parsers this.addFormatParser('gpx', GpxParser); - // this.addFormatParser('xbil', XbilParser); this.addFormatParser('application/json', GeoJsonParser); this.addFormatParser('geojson', GeoJsonParser); this.addFormatParser('json', GeoJsonParser); - // this.addFormatParser('pnts', PntsParser); - // this.addFormatParser('b3dm', B3dmParser); this.addFormatParser('bin', PotreeBinParser); this.addFormatParser('cin', PotreeCinParser); - // this.addFormatParser('gltf', GLTFLoader); - // this.addFormatParser('gltf-legacy', LegacyGLTFLoader); }; Scheduler.prototype.runCommand = function runCommand(command, queue, executingCounterUpToDate) { diff --git a/src/Parser/GpxParser.js b/src/Parser/GpxParser.js index 5e5d721d58..21961ec7e6 100644 --- a/src/Parser/GpxParser.js +++ b/src/Parser/GpxParser.js @@ -166,7 +166,7 @@ function _gpxToMesh(gpxXML, options = {}) { // gpxMesh is static data, it doens't need matrix update gpxMesh.matrixAutoUpdate = false; - return gpxMesh; + return { object3d: gpxMesh }; } export default { From 9a9f01943229eacdc182d23dda572cb934c4537d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Br=C3=A9dif?= Date: Thu, 12 Apr 2018 19:15:11 +0200 Subject: [PATCH 4/7] refactor(parsers): geojson and pnts parser, metadata in parsers --- src/Core/Scheduler/Scheduler.js | 31 ++++++++++++++++++++---------- src/Parser/GeoJsonParser.js | 16 +++++++++------ src/Parser/GpxParser.js | 6 +++++- src/Parser/PntsParser.js | 4 ++++ src/Parser/PotreeBinParser.js | 4 ++++ src/Parser/PotreeCinParser.js | 4 ++++ src/Provider/PointCloudProvider.js | 7 +++---- src/Provider/RasterProvider.js | 2 +- src/Provider/WFSProvider.js | 11 ++++++----- test/feature2mesh_unit_test.js | 2 +- test/featureUtils_unit_test.js | 2 +- 11 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/Core/Scheduler/Scheduler.js b/src/Core/Scheduler/Scheduler.js index b9ee1e3e00..71507d9bae 100644 --- a/src/Core/Scheduler/Scheduler.js +++ b/src/Core/Scheduler/Scheduler.js @@ -21,6 +21,7 @@ import GpxParser from '../../Parser/GpxParser'; import GeoJsonParser from '../../Parser/GeoJsonParser'; import PotreeBinParser from '../../Parser/PotreeBinParser'; import PotreeCinParser from '../../Parser/PotreeCinParser'; +import PntsParser from '../../Parser/PntsParser'; var instanceScheduler = null; @@ -115,12 +116,11 @@ Scheduler.prototype.initDefaultProviders = function initDefaultProviders() { Scheduler.prototype.initDefaultParsers = function initDefaultParsers() { // Register all parsers - this.addFormatParser('gpx', GpxParser); - this.addFormatParser('application/json', GeoJsonParser); - this.addFormatParser('geojson', GeoJsonParser); - this.addFormatParser('json', GeoJsonParser); - this.addFormatParser('bin', PotreeBinParser); - this.addFormatParser('cin', PotreeCinParser); + this.addFormatParser(GpxParser); + this.addFormatParser(GeoJsonParser); + this.addFormatParser(PotreeBinParser); + this.addFormatParser(PotreeCinParser); + this.addFormatParser(PntsParser); }; Scheduler.prototype.runCommand = function runCommand(command, queue, executingCounterUpToDate) { @@ -275,15 +275,26 @@ Scheduler.prototype.getProtocolProvider = function getProtocolProvider(protocol) }; -Scheduler.prototype.addFormatParser = function addParser(format, parser) { +Scheduler.prototype.addFormatParser = function addFormatParser(parser) { + // eslint-disable-next-line no-console + console.log(`Registering format : ${parser.format}`); if (typeof (parser.parse) !== 'function') { - throw new Error(`Can't add parser for ${format}: missing a parse function.`); + throw new Error(`Can't add parser for ${parser.format}: missing a parse function.`); } - this.parsers[format] = parser; + var that = this; + function register(format) { + that.parsers[format] = that.parsers[format] || []; + that.parsers[format].push(parser); + } + register(parser.format); + parser.extensions.forEach(register); + parser.mimetypes.forEach(register); }; Scheduler.prototype.getFormatParser = function getFormatParser(format) { - return this.parsers[format]; + var parsers = this.parsers[format]; + // format disambiguation is arbitrary to the first registration for now + return parsers ? parsers[0] : undefined; }; Scheduler.prototype.commandsWaitingExecutionCount = function commandsWaitingExecutionCount() { diff --git a/src/Parser/GeoJsonParser.js b/src/Parser/GeoJsonParser.js index 514e1cbd21..41b9e2b13d 100644 --- a/src/Parser/GeoJsonParser.js +++ b/src/Parser/GeoJsonParser.js @@ -256,9 +256,9 @@ export default { /** * Parse a GeoJSON file content and return a Feature or an array of Features. - * @param {string} json - The GeoJSON file content to parse. + * @param {object|string} json - The GeoJSON file content to parse. * @param {object} options - options controlling the parsing - * @param {string} options.crsOut - The CRS to convert the input coordinates to. + * @param {string} options.crs - The CRS to convert the input coordinates to. * @param {string} options.crsIn - override the data crs * @param {Extent=} options.filteringExtent - Optional filter to reject features * outside of this extent. @@ -268,19 +268,23 @@ export default { * @returns {Promise} - a promise resolving with a Feature or an array of Features */ parse(json, options = {}) { - const crsOut = options.crsOut; const filteringExtent = options.filteringExtent; if (typeof (json) === 'string') { json = JSON.parse(json); } - options.crsIn = options.crsIn || readCRS(json); + const crsIn = options.crsIn || readCRS(json); + const crsOut = options.crs; switch (json.type.toLowerCase()) { case 'featurecollection': - return Promise.resolve(readFeatureCollection(options.crsIn, crsOut, json, filteringExtent, options)); + return Promise.resolve(readFeatureCollection(crsIn, crsOut, json, filteringExtent, options)); case 'feature': - return Promise.resolve(readFeature(options.crsIn, crsOut, json, filteringExtent, options)); + return Promise.resolve(readFeature(crsIn, crsOut, json, filteringExtent, options)); default: throw new Error(`Unsupported GeoJSON type: '${json.type}`); } }, + format: 'geojson', + extensions: ['json', 'geojson'], + mimetypes: ['application/json', 'application/geojson'], + fetchtype: 'json', }; diff --git a/src/Parser/GpxParser.js b/src/Parser/GpxParser.js index 21961ec7e6..d7084e4a36 100644 --- a/src/Parser/GpxParser.js +++ b/src/Parser/GpxParser.js @@ -173,7 +173,7 @@ export default { /** @module GpxParser */ /** Parse gpx file and convert to THREE.Mesh * @function parse - * @param {string} xml - the gpx file or xml. + * @param {string | XMLDocument} xml - the gpx file or xml. * @param {Object=} options - additional properties. * @param {string} options.crs - the default CRS of Three.js coordinates. Should be a cartesian CRS. * @param {boolean=} [options.enablePin=true] - draw pin for way points. @@ -196,4 +196,8 @@ export default { } return Promise.resolve(_gpxToMesh(xml, options)); }, + format: 'gpx', + extensions: ['gpx', 'xml'], + mimetypes: ['application/gpx+xml', 'application/gpx'], + fetchtype: 'xml', }; diff --git a/src/Parser/PntsParser.js b/src/Parser/PntsParser.js index 8c421b0beb..16a29b0e48 100644 --- a/src/Parser/PntsParser.js +++ b/src/Parser/PntsParser.js @@ -63,6 +63,10 @@ export default { throw new Error('Invalid pnts file.'); } }, + format: '3d-tiles/pnts', + extensions: ['pnts'], + mimetypes: ['application/octet-stream'], + fetchtype: 'arrayBuffer', }; function parseFeatureBinary(array, byteOffset, FTJSONLength) { diff --git a/src/Parser/PotreeBinParser.js b/src/Parser/PotreeBinParser.js index 620c176ded..d1329cd5b9 100644 --- a/src/Parser/PotreeBinParser.js +++ b/src/Parser/PotreeBinParser.js @@ -59,4 +59,8 @@ export default { return Promise.resolve({ object3d: points }); }, + format: 'potree/bin', + extensions: ['bin'], + mimetypes: ['binary/octet-stream'], + fetchtype: 'arrayBuffer', }; diff --git a/src/Parser/PotreeCinParser.js b/src/Parser/PotreeCinParser.js index ed44ee2318..462f2887c5 100644 --- a/src/Parser/PotreeCinParser.js +++ b/src/Parser/PotreeCinParser.js @@ -39,4 +39,8 @@ export default { return Promise.resolve({ object3d: points }); }, + format: 'potree/cin', + extensions: ['cin'], + mimetypes: ['binary/octet-stream'], + fetchtype: 'arrayBuffer', }; diff --git a/src/Provider/PointCloudProvider.js b/src/Provider/PointCloudProvider.js index 8a85599f00..124b596027 100644 --- a/src/Provider/PointCloudProvider.js +++ b/src/Provider/PointCloudProvider.js @@ -135,8 +135,7 @@ function addPickingAttribute(points) { } function loadPointFile(layer, url) { - return fetch(url, layer.fetchOptions) - .then(dat => dat.arrayBuffer()) + return layer.fetcher(url, layer.fetchOptions) .then(buf => layer.parser.parse(buf)) .then(obj => addPickingAttribute(obj.object3d)); } @@ -186,7 +185,6 @@ export default { if (layer.metadata.scale != undefined) { // PotreeConverter format layer.format = layer.format || (layer.metadata.pointAttributes === 'CIN' ? 'cin' : 'bin'); - layer.parser = scheduler.getFormatParser(layer.format); bbox = new THREE.Box3( new THREE.Vector3(cloud.boundingBox.lx, cloud.boundingBox.ly, cloud.boundingBox.lz), new THREE.Vector3(cloud.boundingBox.ux, cloud.boundingBox.uy, cloud.boundingBox.uz)); @@ -208,7 +206,8 @@ export default { new THREE.Vector3(cloud[idx].bbox.xmin, cloud[idx].bbox.ymin, cloud[idx].bbox.zmin), new THREE.Vector3(cloud[idx].bbox.xmax, cloud[idx].bbox.ymax, cloud[idx].bbox.zmax)); } - + layer.parser = scheduler.getFormatParser(layer.format); + layer.fetcher = Fetcher[layer.parser.fetchtype]; return parseOctree( layer, diff --git a/src/Provider/RasterProvider.js b/src/Provider/RasterProvider.js index ff7ef911f0..b0f35096ba 100644 --- a/src/Provider/RasterProvider.js +++ b/src/Provider/RasterProvider.js @@ -105,7 +105,7 @@ export default { const options = { buildExtent: true, crsIn: layer.projection, - crsOut: parentCrs, + crs: parentCrs, filteringExtent: layer.extent, }; diff --git a/src/Provider/WFSProvider.js b/src/Provider/WFSProvider.js index 4d521910c8..b95828cba0 100644 --- a/src/Provider/WFSProvider.js +++ b/src/Provider/WFSProvider.js @@ -6,18 +6,19 @@ import Extent from '../Core/Geographic/Extent'; import URLBuilder from './URLBuilder'; -import Fetcher from './Fetcher'; -import GeoJsonParser from '../Parser/GeoJsonParser'; import Feature2Mesh from '../Renderer/ThreeExtended/Feature2Mesh'; +import Fetcher from './Fetcher'; const cache = new Map(); -function preprocessDataLayer(layer) { +function preprocessDataLayer(layer, view, scheduler) { if (!layer.typeName) { throw new Error('layer.typeName is required.'); } layer.format = layer.format || 'application/json'; + layer.parser = scheduler.getFormatParser(layer.format); + layer.fetch = Fetcher[layer.parser.fetchtype]; layer.crs = layer.projection || 'EPSG:4326'; layer.version = layer.version || '2.0.2'; @@ -69,9 +70,9 @@ function getFeatures(crs, tile, layer) { layer.convert = layer.convert ? layer.convert : Feature2Mesh.convert({}); - return Fetcher.json(urld, layer.networkOptions) + return layer.fetch(urld, layer.networkOptions) .then( - geojson => GeoJsonParser.parse(geojson, { crsOut: crs, filteringExtent: tile.extent, filter: layer.filter }), + data => layer.parser.parse(data, { crs, filteringExtent: tile.extent, filter: layer.filter }), (err) => { // special handling for 400 errors, as it probably means the config is wrong if (err.response.status == 400) { diff --git a/test/feature2mesh_unit_test.js b/test/feature2mesh_unit_test.js index 09c5657d65..7341d8fbc0 100644 --- a/test/feature2mesh_unit_test.js +++ b/test/feature2mesh_unit_test.js @@ -11,7 +11,7 @@ proj4.defs('EPSG:3946', '+proj=lcc +lat_1=45.25 +lat_2=46.75 +lat_0=46 +lon_0=3 +x_0=1700000 +y_0=5200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'); function parse() { - return GeoJsonParser.parse(geojson, { crsIn: 'EPSG:3946', crsOut: 'EPSG:3946', buildExtent: true }); + return GeoJsonParser.parse(geojson, { crsIn: 'EPSG:3946', crs: 'EPSG:3946', buildExtent: true }); } function computeAreaOfMesh(mesh) { diff --git a/test/featureUtils_unit_test.js b/test/featureUtils_unit_test.js index 17a6068f48..fce3fc7b14 100644 --- a/test/featureUtils_unit_test.js +++ b/test/featureUtils_unit_test.js @@ -6,7 +6,7 @@ import Coordinates from '../src/Core/Geographic/Coordinates'; const assert = require('assert'); const geojson = require('./data/geojson/simple.geojson.json'); -const promise = GeoJsonParser.parse(geojson, { crsOut: 'EPSG:4326', buildExtent: true }); +const promise = GeoJsonParser.parse(geojson, { crs: 'EPSG:4326', buildExtent: true }); describe('FeaturesUtils', function () { it('should correctly parse geojson', () => From e94702a2865332083867146b2b5215b254d20dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Br=C3=A9dif?= Date: Thu, 12 Apr 2018 23:23:19 +0200 Subject: [PATCH 5/7] refactor(providers): 3Dtiles, b3dm, pnts use parser registry --- examples/3dtiles.html | 4 +- src/Core/Scheduler/Scheduler.js | 10 +-- src/Parser/3dTilesHeaderParser.js | 81 +++++++++++++++++++++++ src/Parser/B3dmParser.js | 90 +++++++------------------ src/Parser/BatchTableParser.js | 16 ----- src/Parser/PntsParser.js | 106 ++++++++---------------------- src/Provider/3dTilesProvider.js | 101 +++++++++++----------------- 7 files changed, 179 insertions(+), 229 deletions(-) create mode 100644 src/Parser/3dTilesHeaderParser.js delete mode 100644 src/Parser/BatchTableParser.js diff --git a/examples/3dtiles.html b/examples/3dtiles.html index b25c160bce..1d8932bb73 100644 --- a/examples/3dtiles.html +++ b/examples/3dtiles.html @@ -90,7 +90,7 @@ var menuGlobe = new GuiTools('menuDiv', view, 300); - itowns.Fetcher.json('layers/JSONLayers/Ortho.json').then(function (result) { return view.addLayer(result) }); + itowns.Fetcher.json('layers/JSONLayers/OPENSM.json').then(function (result) { return view.addLayer(result) }); // function use : // For preupdate Layer geomtry : @@ -172,7 +172,7 @@ if (interAttributes._BATCHID) { var face = intersects[i].face.a; var batchID = interAttributes._BATCHID.array[face]; - var batchTable = findBatchTableParent(intersects[i].object); + var batchTable = findBatchTableParent(intersects[i].object).json; htmlInfo.innerHTML +='
  • Batch id: '+ batchID +'
  • '; Object.keys(batchTable).map(function(objectKey) { diff --git a/src/Core/Scheduler/Scheduler.js b/src/Core/Scheduler/Scheduler.js index 71507d9bae..d583b92436 100644 --- a/src/Core/Scheduler/Scheduler.js +++ b/src/Core/Scheduler/Scheduler.js @@ -17,11 +17,12 @@ import WFSProvider from '../../Provider/WFSProvider'; import RasterProvider from '../../Provider/RasterProvider'; import StaticProvider from '../../Provider/StaticProvider'; -import GpxParser from '../../Parser/GpxParser'; +import B3dmParser from '../../Parser/B3dmParser'; import GeoJsonParser from '../../Parser/GeoJsonParser'; +import GpxParser from '../../Parser/GpxParser'; +import PntsParser from '../../Parser/PntsParser'; import PotreeBinParser from '../../Parser/PotreeBinParser'; import PotreeCinParser from '../../Parser/PotreeCinParser'; -import PntsParser from '../../Parser/PntsParser'; var instanceScheduler = null; @@ -116,11 +117,12 @@ Scheduler.prototype.initDefaultProviders = function initDefaultProviders() { Scheduler.prototype.initDefaultParsers = function initDefaultParsers() { // Register all parsers - this.addFormatParser(GpxParser); + this.addFormatParser(B3dmParser); this.addFormatParser(GeoJsonParser); + this.addFormatParser(GpxParser); + this.addFormatParser(PntsParser); this.addFormatParser(PotreeBinParser); this.addFormatParser(PotreeCinParser); - this.addFormatParser(PntsParser); }; Scheduler.prototype.runCommand = function runCommand(command, queue, executingCounterUpToDate) { diff --git a/src/Parser/3dTilesHeaderParser.js b/src/Parser/3dTilesHeaderParser.js new file mode 100644 index 0000000000..f77ffae29a --- /dev/null +++ b/src/Parser/3dTilesHeaderParser.js @@ -0,0 +1,81 @@ +import utf8Decoder from '../utils/Utf8Decoder'; + +export default { + /** @module 3dTilesHeaderParser */ + /** Parse buffer and convert to JSON + * @function parse + * @param {ArrayBuffer} buffer - the input buffer. + * @param {Object} options - additional properties. + * @param {string=} [options.magic] - magic string. + * @return {Promise} - a promise that resolves with a 3dTilesHeader object. + * + */ + parse(buffer, options) { + if (!buffer) { + throw new Error('No array buffer provided.'); + } + const header = {}; + const parsed = { header }; + + // Magic type is unsigned char [4] + header.magic = utf8Decoder.decode(new Uint8Array(buffer, 0, 4)); + if (header.magic !== options.magic) { + throw new Error(`Invalid 3d-tiles header : ${header.magic} (${options.magic} was expected).`); + } + + const view = new DataView(buffer); + let byteOffset = 4; + + header.version = view.getUint32(byteOffset, true); + byteOffset += Uint32Array.BYTES_PER_ELEMENT; + + header.byteLength = view.getUint32(byteOffset, true); + byteOffset += Uint32Array.BYTES_PER_ELEMENT; + + if (options.magic === 'cmpt') { + header.tilesLength = view.getUint32(byteOffset, true); + byteOffset += Uint32Array.BYTES_PER_ELEMENT; + } else { + header.FTJSONLength = view.getUint32(byteOffset, true); + byteOffset += Uint32Array.BYTES_PER_ELEMENT; + + header.FTBinaryLength = view.getUint32(byteOffset, true); + byteOffset += Uint32Array.BYTES_PER_ELEMENT; + + header.BTJSONLength = view.getUint32(byteOffset, true); + byteOffset += Uint32Array.BYTES_PER_ELEMENT; + + header.BTBinaryLength = view.getUint32(byteOffset, true); + byteOffset += Uint32Array.BYTES_PER_ELEMENT; + + if (options.magic === 'i3dm') { + header.gltfFormat = view.getUint32(byteOffset, true); + byteOffset += Uint32Array.BYTES_PER_ELEMENT; + } + } + + parsed.header = header; + + if (header.FTJSONLength > 0) { + const json = new Uint8Array(buffer, byteOffset, header.FTJSONLength); + byteOffset += header.FTJSONLength; + parsed.featureTable = { json: JSON.parse(utf8Decoder.decode(json)) }; + if (header.FTBinaryLength > 0) { + parsed.featureTable.buffer = buffer.slice(byteOffset, byteOffset + header.FTBinaryLength); + byteOffset += header.FTBinaryLength; + } + } + + if (header.BTJSONLength > 0) { + const json = new Uint8Array(buffer, byteOffset, header.BTJSONLength); + byteOffset += header.BTJSONLength; + parsed.batchTable = { json: JSON.parse(utf8Decoder.decode(json)) }; + if (header.BTBinaryLength > 0) { + parsed.batchTable.buffer = buffer.slice(byteOffset, header.BTBinaryLength); + byteOffset += header.BTBinaryLength; + } + } + parsed.byteOffset = byteOffset; + return Promise.resolve(parsed); + }, +}; diff --git a/src/Parser/B3dmParser.js b/src/Parser/B3dmParser.js index 8f35999f60..8fbfc4f96c 100644 --- a/src/Parser/B3dmParser.js +++ b/src/Parser/B3dmParser.js @@ -1,9 +1,9 @@ import * as THREE from 'three'; import GLTFLoader from './GLTFLoader'; import LegacyGLTFLoader from './LegacyGLTFLoader'; -import BatchTableParser from './BatchTableParser'; import Capabilities from '../Core/System/Capabilities'; import shaderUtils from '../Renderer/Shader/ShaderUtils'; +import $3dTilesHeaderParser from './3dTilesHeaderParser'; import utf8Decoder from '../utils/Utf8Decoder'; const matrixChangeUpVectorZtoY = (new THREE.Matrix4()).makeRotationX(Math.PI / 2); @@ -63,64 +63,25 @@ export default { * */ parse(buffer, options) { - const gltfUpAxis = options.gltfUpAxis; - const urlBase = options.urlBase; - if (!buffer) { - throw new Error('No array buffer provided.'); - } - - const view = new DataView(buffer, 4); // starts after magic - - let byteOffset = 0; - const b3dmHeader = {}; - - // Magic type is unsigned char [4] - b3dmHeader.magic = utf8Decoder.decode(new Uint8Array(buffer, 0, 4)); - if (b3dmHeader.magic) { - // Version, byteLength, batchTableJSONByteLength, batchTableBinaryByteLength and batchTable types are uint32 - b3dmHeader.version = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - b3dmHeader.byteLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - b3dmHeader.FTJSONLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - b3dmHeader.FTBinaryLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - b3dmHeader.BTJSONLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - b3dmHeader.BTBinaryLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - const promises = []; - if (b3dmHeader.BTJSONLength > 0) { - const sizeBegin = 28 + b3dmHeader.FTJSONLength + b3dmHeader.FTBinaryLength; - promises.push(BatchTableParser.parse( - buffer.slice(sizeBegin, b3dmHeader.BTJSONLength + sizeBegin))); - } else { - promises.push(Promise.resolve({})); - } - // TODO: missing feature table - promises.push(new Promise((resolve/* , reject */) => { - const onload = (gltf) => { + options = options || {}; + options.magic = 'b3dm'; + return $3dTilesHeaderParser.parse(buffer, options).then((data) => { + const gltfBuffer = buffer.slice(data.byteOffset); + const version = new DataView(gltfBuffer, 0, 20).getUint32(4, true); + return new Promise((resolve/* , reject */) => { + function onload(gltf) { for (const scene of gltf.scenes) { scene.traverse(filterUnsupportedSemantics); } // Rotation managed - if (gltfUpAxis === undefined || gltfUpAxis === 'Y') { + if (options.gltfUpAxis === 'Y') { gltf.scene.applyMatrix(matrixChangeUpVectorZtoY); - } else if (gltfUpAxis === 'X') { + } else if (options.gltfUpAxis === 'X') { gltf.scene.applyMatrix(matrixChangeUpVectorZtoX); } // RTC managed - applyOptionalCesiumRTC(buffer.slice(28 + b3dmHeader.FTJSONLength + - b3dmHeader.FTBinaryLength + b3dmHeader.BTJSONLength + - b3dmHeader.BTBinaryLength), gltf.scene); + applyOptionalCesiumRTC(gltfBuffer, gltf.scene); const init_mesh = function f_init(mesh) { mesh.frustumCulled = false; @@ -145,24 +106,21 @@ export default { }; gltf.scene.traverse(init_mesh); - resolve(gltf); - }; - - const gltfBuffer = buffer.slice(28 + b3dmHeader.FTJSONLength + - b3dmHeader.FTBinaryLength + b3dmHeader.BTJSONLength + - b3dmHeader.BTBinaryLength); - - const version = new DataView(gltfBuffer, 0, 20).getUint32(4, true); - + resolve({ + object3d: gltf.scene, + batchTable: data.batchTable, + }); + } if (version === 1) { - legacyGLTFLoader.parse(gltfBuffer, onload, urlBase); + legacyGLTFLoader.parse(gltfBuffer, onload, options.urlBase); } else { - glTFLoader.parse(gltfBuffer, urlBase, onload); + glTFLoader.parse(gltfBuffer, options.urlBase, onload); } - })); - return Promise.all(promises).then(values => ({ gltf: values[1], batchTable: values[0] })); - } else { - throw new Error('Invalid b3dm file.'); - } + }); + }); }, + format: '3d-tiles/b3dm', + extensions: ['b3dm'], + mimetypes: ['application/octet-stream'], + fetchtype: 'arrayBuffer', }; diff --git a/src/Parser/BatchTableParser.js b/src/Parser/BatchTableParser.js deleted file mode 100644 index cd009249ec..0000000000 --- a/src/Parser/BatchTableParser.js +++ /dev/null @@ -1,16 +0,0 @@ -import utf8Decoder from '../utils/Utf8Decoder'; - -export default { - /** @module BatchTableParser */ - /** Parse batch table buffer and convert to JSON - * @function parse - * @param {ArrayBuffer} buffer - the batch table buffer. - * @return {Promise} - a promise that resolves with a JSON object. - * - */ - parse(buffer) { - const content = utf8Decoder.decode(new Uint8Array(buffer)); - const json = JSON.parse(content); - return Promise.resolve(json); - }, -}; diff --git a/src/Parser/PntsParser.js b/src/Parser/PntsParser.js index 16a29b0e48..9f5ae98936 100644 --- a/src/Parser/PntsParser.js +++ b/src/Parser/PntsParser.js @@ -1,67 +1,22 @@ import * as THREE from 'three'; -import BatchTableParser from './BatchTableParser'; -import utf8Decoder from '../utils/Utf8Decoder'; +import $3dTilesHeaderParser from './3dTilesHeaderParser'; export default { /** @module PntsParser */ /** Parse pnts buffer and extract THREE.Points and batch table * @function parse * @param {ArrayBuffer} buffer - the pnts buffer. - * @return {Promise} - a promise that resolves with an object containig a THREE.Points (point) and a batch table (batchTable). + * @param {Object} options - additional properties. + * @return {Promise} - a promise that resolves with an object containig a THREE.Points (object3d) and a batch table (batchTable). * */ - parse: function parse(buffer) { - if (!buffer) { - throw new Error('No array buffer provided.'); - } - const view = new DataView(buffer); - - let byteOffset = 0; - const pntsHeader = {}; - let batchTable = {}; - let point = {}; - - // Magic type is unsigned char [4] - pntsHeader.magic = utf8Decoder.decode(new Uint8Array(buffer, byteOffset, 4)); - byteOffset += 4; - - if (pntsHeader.magic) { - // Version, byteLength, batchTableJSONByteLength, batchTableBinaryByteLength and batchTable types are uint32 - pntsHeader.version = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - pntsHeader.byteLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - pntsHeader.FTJSONLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - pntsHeader.FTBinaryLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - pntsHeader.BTJSONLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - pntsHeader.BTBinaryLength = view.getUint32(byteOffset, true); - byteOffset += Uint32Array.BYTES_PER_ELEMENT; - - // binary table - if (pntsHeader.FTBinaryLength > 0) { - point = parseFeatureBinary(buffer, byteOffset, pntsHeader.FTJSONLength); - } - - // batch table - if (pntsHeader.BTJSONLength > 0) { - const sizeBegin = 28 + pntsHeader.FTJSONLength + pntsHeader.FTBinaryLength; - batchTable = BatchTableParser.parse( - buffer.slice(sizeBegin, pntsHeader.BTJSONLength + sizeBegin)); - } - - const pnts = { point, batchTable }; - return Promise.resolve(pnts); - } else { - throw new Error('Invalid pnts file.'); - } + parse(buffer, options) { + options = options || {}; + options.magic = 'pnts'; + return $3dTilesHeaderParser.parse(buffer, options).then(data => ({ + batchTable: data.batchTable, + object3d: parseFeatureTable(data.featureTable), + })); }, format: '3d-tiles/pnts', extensions: ['pnts'], @@ -69,53 +24,48 @@ export default { fetchtype: 'arrayBuffer', }; -function parseFeatureBinary(array, byteOffset, FTJSONLength) { +function parseFeatureTable(featureTable) { + if (!featureTable) return undefined; // Init geometry const geometry = new THREE.BufferGeometry(); const material = new THREE.PointsMaterial({ size: 0.05, vertexColors: THREE.VertexColors, sizeAttenuation: true }); + const json = featureTable.json; + const buffer = featureTable.buffer; + const POINTS_LENGTH = json.POINTS_LENGTH || 0; - // init Array feature binary - const subArrayJson = utf8Decoder.decode(new Uint8Array(array, byteOffset, FTJSONLength)); - const parseJSON = JSON.parse(subArrayJson); - let lengthFeature; - if (parseJSON.POINTS_LENGTH) { - lengthFeature = parseJSON.POINTS_LENGTH; - } - if (parseJSON.POSITION) { - const byteOffsetPos = (parseJSON.POSITION.byteOffset + subArrayJson.length + byteOffset); - const positionArray = new Float32Array(array, byteOffsetPos, lengthFeature * 3); + if (json.POSITION) { + const positionArray = new Float32Array(buffer, json.POSITION.byteOffset, POINTS_LENGTH * 3); geometry.addAttribute('position', new THREE.BufferAttribute(positionArray, 3)); } - if (parseJSON.RGB) { - const byteOffsetCol = parseJSON.RGB.byteOffset + subArrayJson.length + byteOffset; - const colorArray = new Uint8Array(array, byteOffsetCol, lengthFeature * 3); + if (json.RGB) { + const colorArray = new Uint8Array(buffer, json.RGB.byteOffset, POINTS_LENGTH * 3); geometry.addAttribute('color', new THREE.BufferAttribute(colorArray, 3, true)); } - if (parseJSON.POSITION_QUANTIZED) { + if (json.POSITION_QUANTIZED) { throw new Error('For pnts loader, POSITION_QUANTIZED: not yet managed'); } - if (parseJSON.RGBA) { + if (json.RGBA) { throw new Error('For pnts loader, RGBA: not yet managed'); } - if (parseJSON.RGB565) { + if (json.RGB565) { throw new Error('For pnts loader, RGB565: not yet managed'); } - if (parseJSON.NORMAL) { + if (json.NORMAL) { throw new Error('For pnts loader, NORMAL: not yet managed'); } - if (parseJSON.NORMAL_OCT16P) { + if (json.NORMAL_OCT16P) { throw new Error('For pnts loader, NORMAL_OCT16P: not yet managed'); } - if (parseJSON.BATCH_ID) { + if (json.BATCH_ID) { throw new Error('For pnts loader, BATCH_ID: not yet managed'); } // creation points with geometry and material const points = new THREE.Points(geometry, material); - points.realPointCount = lengthFeature; + points.realPointCount = POINTS_LENGTH; // Add RTC feature - if (parseJSON.RTC_CENTER) { - points.position.fromArray(parseJSON.RTC_CENTER); + if (json.RTC_CENTER) { + points.position.fromArray(json.RTC_CENTER); } return points; diff --git a/src/Provider/3dTilesProvider.js b/src/Provider/3dTilesProvider.js index f1b043ab0d..51d80ee2e5 100644 --- a/src/Provider/3dTilesProvider.js +++ b/src/Provider/3dTilesProvider.js @@ -1,11 +1,8 @@ import * as THREE from 'three'; -import B3dmParser from '../Parser/B3dmParser'; -import PntsParser from '../Parser/PntsParser'; import Fetcher from './Fetcher'; import OBB from '../Renderer/ThreeExtended/OBB'; import Extent from '../Core/Geographic/Extent'; import { init3dTilesLayer } from '../Process/3dTilesProcessing'; -import utf8Decoder from '../utils/Utf8Decoder'; function $3dTilesIndex(tileset, baseURL) { let counter = 0; @@ -114,26 +111,6 @@ function getBox(volume, inverseTileTransform) { } } -function b3dmToMesh(data, layer, url) { - const urlBase = THREE.LoaderUtils.extractUrlBase(url); - const options = { - gltfUpAxis: layer.asset.gltfUpAxis, - urlBase, - overrideMaterials: layer.overrideMaterials, - doNotPatchMaterial: layer.doNotPatchMaterial, - opacity: layer.opacity, - }; - return B3dmParser.parse(data, options).then((result) => { - const batchTable = result.batchTable; - const object3d = result.gltf.scene; - return { batchTable, object3d }; - }); -} - -function pntsParse(data) { - return PntsParser.parse(data).then(result => ({ object3d: result.point })); -} - function configureTile(tile, layer, metadata, parent) { tile.frustumCulled = false; tile.layer = layer.id; @@ -158,59 +135,57 @@ function configureTile(tile, layer, metadata, parent) { } function executeCommand(command) { + const scheduler = command.view.mainLoop.scheduler; const layer = command.layer; const metadata = command.metadata; const tile = new THREE.Object3D(); configureTile(tile, layer, metadata, command.requester); const path = metadata.content ? metadata.content.url : undefined; - const setLayer = (obj) => { + function setLayer(obj) { obj.layers.set(layer.threejsLayer); + } + if (!path) { + tile.traverse(setLayer); + return Promise.resolve(tile); + } + // Check if we have relative or absolute url (with tileset's lopocs for example) + const url = path.startsWith('http') ? path : metadata.baseURL + path; + const format = path.substr(path.lastIndexOf('.') + 1); + + const options = { + gltfUpAxis: layer.asset.gltfUpAxis || 'Y', + urlBase: THREE.LoaderUtils.extractUrlBase(url), + overrideMaterials: layer.overrideMaterials, + doNotPatchMaterial: layer.doNotPatchMaterial, + opacity: layer.opacity, }; - if (path) { - // Check if we have relative or absolute url (with tileset's lopocs for example) - const url = path.startsWith('http') ? path : metadata.baseURL + path; - const supportedFormats = { - b3dm: b3dmToMesh, - pnts: pntsParse, - }; - return Fetcher.arrayBuffer(url, layer.networkOptions).then((result) => { - if (result !== undefined) { - let func; - const magic = utf8Decoder.decode(new Uint8Array(result, 0, 4)); - if (magic[0] === '{') { - result = JSON.parse(utf8Decoder.decode(new Uint8Array(result))); - const newPrefix = url.slice(0, url.lastIndexOf('/') + 1); - layer.tileIndex.extendTileset(result, metadata.tileId, newPrefix); - } else if (magic == 'b3dm') { - func = supportedFormats.b3dm; - } else if (magic == 'pnts') { - func = supportedFormats.pnts; - } else { - Promise.reject(`Unsupported magic code ${magic}`); - } - if (func) { - // TODO: request should be delayed if there is a viewerRequestVolume - return func(result, layer, url).then((content) => { - tile.content = content.object3d; - if (content.batchTable) { - tile.batchTable = content.batchTable; - } - tile.add(content.object3d); - tile.traverse(setLayer); - return tile; - }); - } - } + + if (format === 'json') { + return Fetcher.json(url, layer.networkOptions).then((json) => { + layer.tileIndex.extendTileset(json, metadata.tileId, options.urlBase); tile.traverse(setLayer); return tile; }); - } else { - return new Promise((resolve) => { + } + const parser = scheduler.getFormatParser(format); + if (!parser) { + return Promise.reject(`Unsupported extension ${format} for file ${url} in 3d-tiles`); + } + + return Fetcher[parser.fetchtype](url, layer.networkOptions) + .then(data => parser.parse(data, options)) + .then((content) => { + if (content) { + tile.content = content.object3d; + if (content.batchTable) { + tile.batchTable = content.batchTable; + } + tile.add(content.object3d); + } tile.traverse(setLayer); - resolve(tile); + return tile; }); - } } export default { From 126844ec11062de9f7cd50bde3490536282cd1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Br=C3=A9dif?= Date: Mon, 16 Apr 2018 16:11:23 +0200 Subject: [PATCH 6/7] feat(providers): file provider --- src/Core/Scheduler/Scheduler.js | 2 + src/Parser/3dTilesHeaderParser.js | 2 +- src/Provider/FileProvider.js | 66 +++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/Provider/FileProvider.js diff --git a/src/Core/Scheduler/Scheduler.js b/src/Core/Scheduler/Scheduler.js index d583b92436..2fa0ae9b2b 100644 --- a/src/Core/Scheduler/Scheduler.js +++ b/src/Core/Scheduler/Scheduler.js @@ -16,6 +16,7 @@ import PointCloudProvider from '../../Provider/PointCloudProvider'; import WFSProvider from '../../Provider/WFSProvider'; import RasterProvider from '../../Provider/RasterProvider'; import StaticProvider from '../../Provider/StaticProvider'; +import FileProvider from '../../Provider/FileProvider'; import B3dmParser from '../../Parser/B3dmParser'; import GeoJsonParser from '../../Parser/GeoJsonParser'; @@ -113,6 +114,7 @@ Scheduler.prototype.initDefaultProviders = function initDefaultProviders() { this.addProtocolProvider('wfs', WFSProvider); this.addProtocolProvider('rasterizer', RasterProvider); this.addProtocolProvider('static', StaticProvider); + this.addProtocolProvider('file', FileProvider); }; Scheduler.prototype.initDefaultParsers = function initDefaultParsers() { diff --git a/src/Parser/3dTilesHeaderParser.js b/src/Parser/3dTilesHeaderParser.js index f77ffae29a..ce36ad43d0 100644 --- a/src/Parser/3dTilesHeaderParser.js +++ b/src/Parser/3dTilesHeaderParser.js @@ -20,7 +20,7 @@ export default { // Magic type is unsigned char [4] header.magic = utf8Decoder.decode(new Uint8Array(buffer, 0, 4)); if (header.magic !== options.magic) { - throw new Error(`Invalid 3d-tiles header : ${header.magic} (${options.magic} was expected).`); + throw new Error(`Invalid 3d-tiles header : "${header.magic}" ("${options.magic}" was expected).`); } const view = new DataView(buffer); diff --git a/src/Provider/FileProvider.js b/src/Provider/FileProvider.js new file mode 100644 index 0000000000..047421cff5 --- /dev/null +++ b/src/Provider/FileProvider.js @@ -0,0 +1,66 @@ +import * as THREE from 'three'; +import Feature2Mesh from '../Renderer/ThreeExtended/Feature2Mesh'; + +function readFile(file, type) { + return new Promise((resolve, reject) => { + var fr = new FileReader(); + fr.onload = () => { resolve(fr.result); }; + fr.onerror = () => { fr.abort(); reject(new DOMException('FileReader error.')); }; + if (type === 'arrayBuffer') { + fr.readAsArrayBuffer(file); + } else { + fr.readAsBinaryString(file); + } + }); +} + +function assignLayer(object, layer) { + if (object) { + object.layer = layer.id; + object.layers.set(layer.threejsLayer); + for (const c of object.children) { + assignLayer(c, layer); + } + return object; + } +} + +export default { + preprocessDataLayer(layer, view, scheduler) { + const file = layer.file; + const parser = layer.parser + || scheduler.getFormatParser(layer.format) + || scheduler.getFormatParser(file.type) + || scheduler.getFormatParser(file.name.split('.').pop().toLowerCase()); + if (!parser) + { + throw new Error(`No parser available for file "${file.name}"`); + } + var options = layer.options || {}; + options.crs = view.referenceCrs; + + layer.name = layer.name || file.name; + layer.update = layer.update || (() => {}); + layer.convert = layer.convert ? layer.convert : Feature2Mesh.convert(options); + layer.object3d = layer.object3d || new THREE.Group(); + layer.threejsLayer = layer.threejsLayer || view.mainLoop.gfxEngine.getUniqueThreejsLayer(); + + function addObject(obj) { + if (obj && !obj.object3d) { + obj.object3d = layer.convert(obj); + } + if (obj && obj.object3d) { + layer.object3d.add(obj.object3d); + assignLayer(obj.object3d, layer); + view.camera.camera3D.layers.enable(layer.threejsLayer); + view.notifyChange(true); + } else { + console.warn(obj, ' has no object3d key'); + } + return obj; + } + return readFile(file, parser.fetchtype).then(content => parser.parse(content, options)).then(addObject); + }, + + executeCommand(/* command */) {}, +}; From b8cd85cee4eaaa9e9ae610844f25a9c5f704f8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Br=C3=A9dif?= Date: Mon, 16 Apr 2018 16:12:07 +0200 Subject: [PATCH 7/7] feat(examples): new file drop example --- examples/file.html | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 examples/file.html diff --git a/examples/file.html b/examples/file.html new file mode 100644 index 0000000000..3ed0e70d9c --- /dev/null +++ b/examples/file.html @@ -0,0 +1,72 @@ + + + Itowns - File drop + + + + + + + +
    + + + + +