{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["tabs","tab"]},"type":"markdown"},"seo":{"title":"Custom Remote Reader","description":"Accelerate E&P application development and protect your innovation by consuming our Data and Domain APIs / Platform APIs.","lang":"en-US","meta":[{"name":"robots","content":"noindex"}],"llmstxt":{"hide":true,"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"custom-remote-reader","__idx":0},"children":["Custom Remote Reader"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["INTGeoServer provides a set of web services to retrieve geo data for web and standalone applications. GeoToolkit includes",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RemoteSeismicReader"]}," in the seismic module to get seismic data from the INTGeoServer. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RemoteSeismicReader"]}," allows replacing \"transport\" protocol between server and client. This tutorial shows how to create a custom reader to read seismic data based on customization of transport protocol in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RemoteSeismicReader"]},". For demonstration purposes only, node.js is used on the server side in addition to some classes from GeoToolkit to read SEG-Y format. Using the same server-side implementation in production is not recommended, because it was created to show server and client communication only. This tutorial implements an API similar to INTGeoServer to read SEG-Y files located on the server."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"create-a-reader","__idx":1},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#/Seismic/Readers/CustomRemoteReader/customRemoteReader#Reader"},"children":["#"]}," Create a Reader"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["First, provide a location of the server and a file to be visualized. Specify the version of protocol that is going to be called. In the current example, a local server and a custom protocol will be used. Install all npm packages and run the node server first.",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},"To do this:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Go to the server folder inside the customremotereader folder and run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm install"]},". Check to ensure the latest LTS version of node.js is installed."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Run node server.js"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To run server-side code, install the following prerequisites:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://nodejs.org"},"children":["node.js"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://www.npmjs.com/package/canvas"},"children":["node-canvas"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The code below creates a RemoteSeismicDataSource that returns information from the remote server about the specified seismic data source. It includes meta information and a collection of keys, which can be utilized to make a query. This server server implementation doesn't support keys. Call the method ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["open()"]}," to receive information from the server. This call is asynchronous and one of two methods will be called in case of success or failure. Next, create a query from the server using the \"select\" method. In the current example, the \"empty query\" is used to request all traces. This method returns an instance of the RemoteSeismicReader to be provided as parameter for a visualization pipeline. The reader retrieves groups of traces from the server by requests from pipeline. It never loads all traces to memory. As a rule, it keeps some traces in the memory cache and retrieves groups of traces to be visible in the specified widget. This example specifies a protocol ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node"]}," in the version field, which is discussed in more detail later."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"display-data","__idx":2},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#/Seismic/Readers/CustomRemoteReader/customRemoteReader#VisualizeData"},"children":["#"]}," Display Data"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The next step is similar to the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#/Seismic/Readers/remoteReader"},"children":["Remote Seismic Reader"]}," tutorial. Create a pipeline and widget to display data."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"create-a-custom-protocol","__idx":3},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#/Seismic/Readers/CustomRemoteReader/customRemoteReader#Protocol"},"children":["#"]}," Create a Custom Protocol"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To provide a custom protocol of data communication between client and server, first implement ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["geotoolkit/seismic/data/RemoteReaderDataProvider"]}," and register it in the",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["geotoolkit/seismic/data/RemoteReaderDataProviderRegistry"]},". Register it by the name \"node.\"",{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RemoteReaderDataProvider"]}," requests to implement several methods:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["open"]}," - return information about data source from the server"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["queryTraces"]}," - make a query based on the selected keys and return information on how many traces are in the result set"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["readTraces"]}," - return a set of the requested traces from the server in the binary format."]}]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"main","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import {RemoteReaderDataProviderRegistry} from '@int/geotoolkit/seismic/data/RemoteReaderDataProviderRegistry';\nimport {obfuscate} from '@int/geotoolkit/lib';\nimport {HttpClient} from '@int/geotoolkit/http/HttpClient';\nimport {RemoteReaderDataProvider} from '@int/geotoolkit/seismic/data/RemoteReaderDataProvider';\nclass NodeServerDataProvider extends RemoteReaderDataProvider {\n    constructor (options) {\n        super();\n        this.options = options;\n        this.http = HttpClient.getInstance().getHttp();\n    }\n\n    createInstance (options) {\n        return new NodeServerDataProvider(options);\n    }\n\n    open (fileName) {\n        return this.http.get('seismicdata/' + encodeURIComponent(fileName), {\n            'responseType': 'json',\n            'baseURL': this.options['host']\n        }).then(function (response) {\n            if (response['data'] && response['data']['version']) {\n                return response['data'];\n            }\n            return Promise.reject('Server error');\n        }, function (error) {\n            return Promise.reject('Cannot connect to the server! Run node server.js!');\n        });\n    }\n\n    queryTraces (fileName, query) {\n        return this.http.get('seismicquery/' + encodeURIComponent(fileName), {\n            'responseType': 'json',\n            'baseURL': this.options['host']\n        }).then(function (response) {\n            if (response['data'] && response['data']['version']) {\n                return response['data'];\n            }\n            return Promise.reject('Server error');\n        }, function (error) {\n            return Promise.reject('Cannot connect to the server! Run node server.js!');\n        });\n    }\n\n    readTraces (fileName, options) {\n        if (options && !Array.isArray(options['traceIndexes'])) {\n            options['traceIndexes'] = [];\n            for (let i = options['from']; i <= options['to']; ++i) {\n                options['traceIndexes'].push(i);\n            }\n        }\n        return this.http.request({\n            'url': 'enumeratedtraces',\n            'baseURL': this.options['host'],\n            'method': 'POST',\n            'responseType': 'arraybuffer',\n            'headers': {\n                'Content-Type': 'application/json'\n            },\n            'data': {\n                'file': fileName,\n                'byteOrder': options['byteOrder'],\n                'query': options['query'],\n                'data': {\n                    'byteOrder': options['byteOrder'],\n                    'samples': options['samples'],\n                    'headers': options['headers'],\n                    'traceIndexes': options['traceIndexes']\n                }\n            },\n            'transformResponse': function (response) {\n                return response['data'];\n            }\n        });\n    }\n}\n\nobfuscate(NodeServerDataProvider);\nRemoteReaderDataProviderRegistry.getInstance().register('node', new NodeServerDataProvider());\n","lang":"ts"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"server-implementation","__idx":4},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#/Seismic/Readers/CustomRemoteReader/customRemoteReader#Server"},"children":["#"]}," Server Implementation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The server side is based on the node.js and express frameworks."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The file ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["server.js"]}," contains a standard initialization for express. Enable ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CORS"]}," and load routes."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"main","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import express from 'express';\nimport methodOverride from 'method-override';\nimport bodyParser from 'body-parser';\nimport errorHandler from 'errorhandler';\nimport '@int/geotoolkit/environment.js';\nimport {isWorker} from './cluster.js';\nimport {routes} from './routes';\n\n// If isWorker is true then this thread is a worker thread and should start working\nif (isWorker) {\n    global.consoleLog('===============================================================\\n');\n    global.consoleLog('> LOADING ... \\n');\n    startHTTPServer();\n}\nfunction startHTTPServer () {\n    let app = express();\n    const PORT = 3001;\n\n    app.listen(PORT, () => {\n        app.use(bodyParser.json());\n        app.use(methodOverride());\n        app.use(bodyParser.urlencoded({extended: true}));\n\n        app.all('/*', function (req, res, next) {\n            // CORS headers\n            res.header('Access-Control-Allow-Origin', '*'); // restrict it to the required domain\n            res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');\n            // Set custom headers for CORS\n            res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key');\n            if (req.method === 'OPTIONS') {\n                res.status(200).end();\n            } else {\n                next();\n            }\n        });\n        // routes ======================================================================\n        routes(app); // load our routes and pass in our app and fully configured passport\n\n        // error handling middleware should be loaded after the loading the routes\n        if (app.get('env') === 'development') {\n            app.use(errorHandler());\n        }\n        console.log('Express server listening on port ' + PORT);\n    });\n}\n","lang":"ts"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"routes","__idx":5},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#/Seismic/Readers/CustomRemoteReader/customRemoteReader#Routes"},"children":["#"]}," Routes"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This example uses GeoToolkit.JS on the server-side as well as node.js. To do this, use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jsdom"]}," and browser bridge to emulate a desktop browser."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Node.js doesn't support FileAPI of the usual browser. In this case, provide an imlemenation of the localfile support based on the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fs"]}," API."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The servers process three routes: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["seismicdata"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["seismicquery"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["enumeratetraces"]},"."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"main","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import '@int/geotoolkit/environment.js';\nimport {NodeExport} from '@int/geotoolkit/scene/exports/NodeExport';\nimport {GeometryUtil} from '@int/geotoolkit/util/GeometryUtil';\nimport {Path} from '@int/geotoolkit/scene/shapes/Path';\nimport {Group} from '@int/geotoolkit/scene/Group';\nimport {Rect} from '@int/geotoolkit/util/Rect';\nimport {Transformation} from '@int/geotoolkit/util/Transformation';\nimport {NormalizationType} from '@int/geotoolkit/seismic/pipeline/NormalizationType';\nimport {SeismicPipeline} from '@int/geotoolkit/seismic/pipeline/SeismicPipeline';\nimport {StandardSegyFormat} from '@int/geotoolkit/seismic/data/StandardSegyFormat';\nimport {SegyReader} from '@int/geotoolkit/seismic/data/SegyReader';\nimport {LocalFile} from '@int/geotoolkit/seismic/data/LocalFile';\nimport {DataFormatType} from '@int/geotoolkit/seismic/data/DataFormatType';\n\n\nlet createCanvas = __require_for_vite_BGaudh.default || __require_for_vite_BGaudh.createCanvas;\n\nlet gCanvas = null;\nlet gSeismicDefaults = {\n    'colormap': 'WhiteBlack',\n    'width': 1024,\n    'height': 1024\n};\nlet Status = {\n    OK: 200,\n    Error: 500\n};\nlet parseType = function (type) {\n    switch (type) {\n        case DataFormatType.UInt:\n            return 'UInt';\n        case DataFormatType.Short:\n            return 'Short';\n        case DataFormatType.Int:\n            return 'Int';\n        case DataFormatType.UShort:\n            return 'UShort';\n        case DataFormatType.Float:\n            return 'Float';\n        case DataFormatType.Double:\n            return 'Double';\n    }\n    return 'Unknown';\n};\nlet parseSize = function (type) {\n    switch (type) {\n        case DataFormatType.UInt:\n        case DataFormatType.Int:\n            return 4;\n        case DataFormatType.Short:\n        case DataFormatType.UShort:\n            return 2;\n        case DataFormatType.Float:\n            return 4;\n        case DataFormatType.Double:\n            return 8;\n    }\n    return 0;\n};\nlet getSeismicHeaderSize = function (reader) {\n    let fields = reader.getTraceHeaderFields();\n    let field, size = 0, fieldsize;\n    for (let i = 0; i < fields.length; ++i) {\n        field = fields[i];\n        fieldsize = parseSize(field.getDataType());\n        size += fieldsize;\n    }\n    return size;\n};\nlet getSeismicFormat = function (reader) {\n    let fields = reader.getTraceHeaderFields();\n    let result = [], field, size = 0, fieldsize;\n    for (let i = 0; i < fields.length; ++i) {\n        field = fields[i];\n        fieldsize = parseSize(field.getDataType());\n        result.push({'name': field.getName(), 'id': i, 'type': parseType(field.getDataType()), 'size': fieldsize});\n        size += fieldsize;\n    }\n    return {\n        'fields': result,\n        'size': size\n    };\n};\nlet openSeismicFile = function (fileName) {\n    return new Promise(function (resolve, reject) {\n        let file = new LocalFile(fileName);\n        let reader = new SegyReader(file, new StandardSegyFormat(), -6, 0);\n        reader.loadMetaData(function (reader) {\n            resolve(reader);\n        });\n    });\n};\nlet readStatistics = function (reader) {\n    return new Promise(function (resolve, reject) {\n        reader.readDataSetStatistics(function (reader, statistics) {\n            resolve(statistics);\n        });\n    });\n};\nlet writeFieldValue = function (buffer, value, type, offset, le) {\n    switch (type) {\n        case DataFormatType.UInt: {\n            if (le) {\n                buffer.writeUInt32LE(value, offset);\n            } else {\n                buffer.writeUInt32BE(value, offset);\n            }\n            break;\n        }\n        case DataFormatType.Int: {\n            if (le) {\n                buffer.writeInt32LE(value, offset);\n            } else {\n                buffer.writeInt32BE(value, offset);\n            }\n            break;\n        }\n        case DataFormatType.Short: {\n            if (le) {\n                buffer.writeInt16LE(value, offset, true);\n            } else {\n                buffer.writeInt16BE(value, offset, true);\n            }\n            break;\n        }\n        case DataFormatType.UShort: {\n            if (le) {\n                buffer.writeUInt16LE(value, offset);\n            } else {\n                buffer.writeUInt16BE(value, offset);\n            }\n            break;\n        }\n        case DataFormatType.Float: {\n            if (le) {\n                buffer.writeFloatLE(value, offset);\n            } else {\n                buffer.writeFloatBE(value, offset);\n            }\n            break;\n        }\n        case DataFormatType.Double: {\n            if (le) {\n                buffer.writeDoubleLE(value, offset);\n            } else {\n                buffer.writeDoubleBE(value, offset);\n            }\n            break;\n        }\n    }\n};\nlet printTraceLE = function (reader, trace, buffer, offset) {\n    let traceHeaderFields = reader.getTraceHeaderFields();\n    let i;\n    for (i = 0; i < traceHeaderFields.length; ++i) {\n        let headerValue = trace.getHeader(i);\n        let field = traceHeaderFields[i];\n        let size = parseSize(field.getDataType());\n        writeFieldValue(buffer, headerValue, field.getDataType(), offset, true);\n        offset += size;\n    }\n    let samples = trace.getSamples();\n    for (i = 0; i < reader.getNumberOfSamples(); ++i) {\n        buffer.writeFloatLE(samples[i], offset);\n        offset += 4;\n    }\n    return offset;\n};\nlet printTraceBE = function (reader, trace, buffer, offset) {\n    let traceHeaderFields = reader.getTraceHeaderFields();\n    let i;\n    for (i = 0; i < traceHeaderFields.length; ++i) {\n        let headerValue = trace.getHeader(i);\n        let field = traceHeaderFields[i];\n        let size = parseSize(field.getDataType());\n        writeFieldValue(buffer, headerValue, field.getDataType(), offset, false);\n        offset += size;\n    }\n    let samples = trace.getSamples();\n    for (i = 0; i < reader.getNumberOfSamples(); ++i) {\n        buffer.writeFloatLE(samples[i], offset);\n        offset += 4;\n    }\n    return offset;\n};\nlet generateSeismicImage = function (options) {\n    return new Promise(function (resolve, reject) {\n        openSeismicFile(options['file']).then(function (reader) {\n            readStatistics(reader).then(function (statistics) {\n                let pipeline = new SeismicPipeline({\n                    'name': 'ExportImage',\n                    'reader': reader,\n                    'statistics': statistics\n                });\n                pipeline.setOptions({\n                    'transparency': false,\n                    'plot': {\n                        'type': {\n                            'Wiggle': false,\n                            'InterpolatedDensity': true\n                        },\n                        'decimationSpacing': 0,\n                        'clippingFactor': 0\n                    },\n                    'normalization': {\n                        'type': NormalizationType.RMS,\n                        'scale': 0.4\n                    },\n                    'colors': {\n                        'colorMap': options['colormap']\n                    }\n                });\n\n                let model = reader.getModelLimits();\n                let limitsToModelTransformation = Transformation\n                    .getRectToRectInstance(model, options['modelInDevice'], false, false, false);\n                let limits = limitsToModelTransformation.inverseTransformRect(options['tile']);\n                let tracesModelSpace = limits.intersect(reader.getModelLimits());\n\n                let canvaselement = options['canvas'];\n                let deviceSpace = new Rect(\n                    0, 0, canvaselement.width, canvaselement.height\n                );\n                pipeline.exportToImage(\n                    tracesModelSpace,\n                    canvaselement,\n                    deviceSpace,\n                    0,\n                    0,\n                    function () {\n                        resolve(canvaselement);\n                    }\n                );\n            }, function (error) {\n                reject(error);\n            });\n        },\n        function (error) {\n            reject(error);\n        }\n        );\n    });\n};\nexport const routes = function (app) {\n    // Gets a file information\n    app.get('/seismicdata/:file', function (req, res) {\n        openSeismicFile(req.params['file']).then(function (reader) {\n            readStatistics(reader).then(function (statistics) {\n                res.json({\n                    'version': 1,\n                    'displayName': req.params['file'],\n                    'numberOfSamples': reader.getNumberOfSamples(),\n                    'numberOfTraces': reader.getNumberOfTraces(),\n                    'startValue': 0,\n                    'sampleRate': reader.getSampleRate(),\n                    'statistics': statistics,\n                    'traceHeader': getSeismicFormat(reader)\n                });\n            }, function (error) {\n                res.error(error);\n            });\n        },\n        function (error) {\n            res.error(error);\n        }\n        );\n    });\n    app.get('/seismicquery/:file', function (req, res) {\n        openSeismicFile(req.params['file']).then(function (reader) {\n            readStatistics(reader).then(function (statistics) {\n                res.json({\n                    'version': 1,\n                    'numberOfSamples': reader.getNumberOfSamples(),\n                    'numberOfTraces': reader.getNumberOfTraces(),\n                    'startValue': 0,\n                    'sampleRate': reader.getSampleRate(),\n                    'statistics': statistics\n                });\n            }, function (error) {\n                res.error(error);\n            });\n        },\n        function (error) {\n            res.error(error);\n        }\n        );\n    });\n    app.post('/enumeratedtraces', function (req, res) {\n        let body = req.body;\n        let byteOrder = body['byteOrder'] != null ? body['byteOrder'] : 'LITTLE_ENDIAN';\n        let printTrace = byteOrder === 'LITTLE_ENDIAN' ? printTraceLE : printTraceBE;\n        let data = body['data'];\n        openSeismicFile(body['file']).then(function (reader) {\n            let headerLength = getSeismicHeaderSize(reader);\n            let length = (data['traceIndexes'].length * (reader.getNumberOfSamples() * 4 + headerLength));\n            let arr = new Uint8Array(length);\n            let buffer = Buffer.from(arr.buffer);\n            res.writeHead(Status.OK, {\n                'Content-Type': 'application/octet-stream', 'Content-Length': length\n            });\n            reader.select({\n                'traceIndexes': data['traceIndexes'] != null ? data['traceIndexes'] : [],\n                'headers': true,\n                'samples': true\n            }, function (context) {\n                let offset = 0;\n                context.foreach(function (id, section) {\n                    for (let i = 0; i < section.getNumberOfTraces(); ++i) {\n                        let trace = section.getTraceByIndex(i);\n                        offset = printTrace(reader, trace, buffer, offset, headerLength);\n                    }\n                });\n                res.end(buffer);\n            });\n        });\n    });\n\n    // Generate seismic image\n    app.get('/seismicimage/:file', function (req, res) {\n        let file = decodeURIComponent(req.params['file']);\n        let x = +req.query['x'], y = +req.query['y'], cropWidth = +req.query['cropWidth'], cropHeight = +req.query['cropHeight'];\n        let imgW = +req.query['imgW'], imgH = +req.query['imgH'];\n        let tile = new Rect(x, y, x + cropWidth, y + cropHeight);\n        let modelInDevice = new Rect(0, 0, imgW, imgH);\n        let colormap = gSeismicDefaults['colormap'];\n        let imageWidth = tile.getWidth();\n        let imageHeight = tile.getHeight();\n        if (gCanvas == null || gCanvas.width !== imageWidth || gCanvas.height !== imageHeight) {\n            let canvas = createCanvas();\n            canvas.height = imageHeight;\n            canvas.width = imageWidth;\n            gCanvas = canvas;\n        }\n        generateSeismicImage({\n            'canvas': gCanvas,\n            'file': file,\n            'width': imageWidth,\n            'height': imageHeight,\n            'colormap': colormap,\n            'modelInDevice': modelInDevice,\n            'tile': tile,\n            'errors': []\n        }).then(function (png) {\n            res.writeHead(200, {'Content-Type': 'image/png'});\n            res.end(png.toBuffer()); // Send the file data to the browser.\n        }, function (m) {\n            res.status(Status.Error).send(m);\n        });\n    });\n    // Generate dynamic velocity image\n    app.get('/generatedynamicimage/:file', function (req, res) {\n        let x = +req.query['x'], y = +req.query['y'], cropWidth = +req.query['cropWidth'], cropHeight = +req.query['cropHeight'];\n        let lx = +req.query['lx'], ly = +req.query['ly'], lwidth = +req.query['lwidth'], lheight = +req.query['lheight'];\n        let imgW = +req.query['imgW'], imgH = +req.query['imgH'];\n        let tile = new Rect(x, y, x + cropWidth, y + cropHeight);\n        let model = new Rect(lx, ly, lx + lwidth, ly + lheight);\n        let modelInDevice = new Rect(0, 0, imgW, imgH);\n\n        let limitsToModelTransformation = Transformation.getRectToRectInstance(model, modelInDevice, false, false, false);\n        let limits = limitsToModelTransformation.inverseTransformRect(tile);\n\n        let tileInModel = new Rect(limits);\n        let tileInDeviceRect = new Rect(tile);\n        // The full model\n        let srcLimits = model;\n        limitsToModelTransformation = Transformation.getRectToRectInstance(tileInModel, tileInDeviceRect, false, false, false);\n        // generate scene graph\n        let stepWidth = 1;\n        let waveWidth = srcLimits.getWidth() / 5; // waveWidth => Math.PI;\n        let getVelocityValue = function (x, velocity, snapToStep, step) {\n            if (step == null) {\n                step = stepWidth;\n            }\n            let xValue = x / waveWidth * Math.PI;\n            if (snapToStep) {\n                x = ((x / step) >> 0) * step;\n            }\n            velocity.x = x;\n            velocity.y = 1000 + Math.sin(xValue) * 100;\n            return velocity;\n        };\n\n\n        let buildSceneGraphD = function (srcLimitsD, stepD) {\n            let sceneGraphD = new Group()\n                .setModelLimits(srcLimitsD);\n\n            let pathD = new Path()\n                .setLineStyle({\n                    'width': 1,\n                    'color': 'red',\n                    'pixelsnapmode': {'x': true, 'y': true}\n                })\n                .setFillStyle('rgba(255, 216, 0, 0.5)')\n                .moveTo(srcLimitsD.getLeft(), srcLimitsD.getBottom());\n            let velocityD = {\n                x: srcLimitsD.getLeft(),\n                y: 0\n            };\n            let iD;\n            for (iD = srcLimitsD.getLeft(); iD < srcLimitsD.getRight(); iD += stepD) {\n                if (velocityD.y !== 0) {\n                    pathD.lineTo(velocityD.x + stepD, velocityD.y);\n                }\n                velocityD = getVelocityValue(iD, velocityD, true, stepD);\n                pathD.lineTo(velocityD.x, velocityD.y);\n            }\n            pathD.lineTo(iD, srcLimitsD.getBottom())\n                .lineTo(srcLimitsD.getLeft(), srcLimitsD.getBottom())\n                .close();\n            sceneGraphD.addChild(pathD);\n            return sceneGraphD;\n        };\n\n        let sceneGraph = buildSceneGraphD(srcLimits, stepWidth);\n        let surface = null;\n        if (req.query['dynamic'] === true) {\n            // build dynamic model\n            let stepD = GeometryUtil.getVectorLengthInModel(1, 0, limitsToModelTransformation);\n            let srcLimitsD = tileInModel.clone().inflate(stepD * 2);\n            let sceneGraphD = buildSceneGraphD(srcLimitsD, stepD);\n            surface = NodeExport\n                .exportToSurface(sceneGraphD, tileInDeviceRect.getWidth(), tileInDeviceRect.getHeight(), false, false, tileInModel);\n        } else {\n            surface = NodeExport\n                .exportToSurface(sceneGraph, tileInDeviceRect.getWidth(), tileInDeviceRect.getHeight(), false, false, tileInModel);\n        }\n        let canvas = surface.getCanvas();\n        res.writeHead(200, {'Content-Type': 'image/png'});\n        res.end(canvas.toBuffer());\n    });\n};\n","lang":"ts"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"result","__idx":6},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#/Seismic/Readers/CustomRemoteReader/customRemoteReader#Result"},"children":["#"]}," Result"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The following example uses ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RemoteSeismicReader"]}," to read data from the server. All traces are retrieved from the server and displayed with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SeismicWidget"]},". The canvas below shows a result of the visualization. Steps to set up the Custom Remote Reader follow."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"main","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import { Plot } from \"@int/geotoolkit/plot/Plot.ts\";\nimport { SeismicWidget } from \"@int/geotoolkit/seismic/widgets/SeismicWidget.ts\";\nimport { SeismicColors } from \"@int/geotoolkit/seismic/util/SeismicColors.ts\";\nimport { NormalizationType } from \"@int/geotoolkit/seismic/pipeline/NormalizationType.ts\";\nimport { SeismicPipeline } from \"@int/geotoolkit/seismic/pipeline/SeismicPipeline.ts\";\nimport { RemoteSeismicDataSource } from \"@int/geotoolkit/seismic/data/RemoteSeismicDataSource.ts\";\nimport \"/src/code/Seismic/Readers/CustomRemoteReader/nodeServerDataProvider.ts\";\nconst createReader = function(onready, onfailure) {\n  const host = \"http://localhost:3001/\";\n  const data = new RemoteSeismicDataSource({\n    \"host\": host,\n    \"file\": \"data/section.segy\",\n    \"version\": \"node\"\n  });\n  data.open(\n    () => {\n      data.select({}, (reader) => {\n        onready(reader);\n      });\n    },\n    (err) => {\n      onfailure(err);\n    }\n  );\n};\nconst createPipeline = function(reader) {\n  const pipeline = new SeismicPipeline({\n    \"name\": \"Seismic\",\n    \"reader\": reader,\n    \"statistics\": reader.getStatistics()\n  });\n  pipeline.setOptions({\n    \"normalization\": {\n      \"type\": NormalizationType.RMS,\n      \"scale\": 0.4\n    },\n    \"plot\": {\n      \"type\": {\n        \"wiggle\": false,\n        \"interpolateddensity\": true\n      },\n      \"decimationspacing\": 5\n    },\n    \"colors\": {\n      \"colormap\": SeismicColors.getDefault().createNamedColorMap(\"RedWhiteBlack\")\n    }\n  });\n  return pipeline;\n};\nfunction createScene(canvas, onError) {\n  const widget = new SeismicWidget({\n    \"colorbar\": {\n      \"axis\": {\n        \"tickgenerator\": {\n          \"edge\": {\n            \"tickvisible\": false,\n            \"labelvisible\": false\n          }\n        }\n      }\n    }\n  });\n  createReader((reader) => {\n    const pipeline = createPipeline(reader);\n    widget.setPipeline(pipeline);\n    widget.setOptions({\n      \"layouttype\": \"inside\",\n      \"statusbar\": {\n        \"visible\": false\n      }\n    });\n    const headerFields = pipeline.getReader().getTraceHeaderFields();\n    let headerInfo;\n    for (let i = 0; i < headerFields.length; i++) {\n      const header = headerFields[i];\n      if (header.getName() === \"SRCX\") {\n        widget.setTraceHeaderVisible(header, true);\n      } else if (header.getName() === \"TraceNumber\") {\n        widget.setTraceHeaderVisible(header, false);\n      }\n      headerInfo = widget.getTraceHeaderAxis(header);\n      if (headerInfo) {\n        headerInfo[\"label\"].getTextStyle().setColor(\"#6b6b6b\");\n      }\n    }\n  }, (err) => {\n    onError(true);\n  });\n  return new Plot({\n    \"canvaselement\": canvas,\n    \"root\": widget\n  });\n}\nexport { createScene };\n\ncreateScene(document.querySelector('[ref=\"plot\"]'), this.inputServerWarningDialog);\n\n","lang":"ts"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"css","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"css","header":{"controls":{"copy":{}}},"source":"\n.cg-tooltip-holder {\n  position: relative;\n}\n\n.cg-tooltip {\n  position: absolute;\n  display: block;\n  padding: 2px 12px 3px 7px;\n  overflow: visible !important;\n  font-family: Roboto, Helvetica, Arial, sans-serif;\n  font-size: 13px;\n  background: white !important;\n  opacity: 0.9;\n  color: #333333;\n  border: solid 1px gray;\n  border-radius: 5px;\n  text-align: left;\n  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);\n  border-radius: 5px;\n  margin: 0 !important;\n  z-index: 10000;\n  max-width: 400px;\n  text-wrap: normal !important;\n  white-space: normal !important;\n}\n/* Default setting for tooltip */\n.cg-tooltip-container {\n  position: absolute;\n  display: block;\n  overflow: visible !important;\n  font-family: Roboto, Helvetica, Arial, sans-serif;\n  font-size: 12px;\n  padding: 3px 7px;\n  background: #f7f7f7;\n  color: #333333;\n  border: 1px solid #938e8e;\n  opacity: 0.8;\n  text-align: left;\n  box-shadow: 3px 3px 10px #888;\n  margin: 0 !important;\n  z-index: 10000;\n  max-width: 400px;\n  text-wrap: normal !important;\n  white-space: normal !important;\n  user-select: none;\n}\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n  .cg-tooltip-container {\n    border-radius: 0;\n  }\n}\n/* Default left arrow for tooltip */\n.cg-tooltip-arrow-left::before {\n  content: '';\n  position: absolute;\n  display: block;\n  width: 0px;\n  left: 0;\n  top: 50%;\n  border: 5px solid transparent;\n  border-left: 0;\n  border-right: 5px solid  #938e8e;\n  transform: translate(calc(-100%), -50%);\n}\n.cg-tooltip-arrow-left::after {\n  content: '';\n  position: absolute;\n  display: block;\n  width: 0px;\n  left: 0;\n  top: 50%;\n  border: 4px solid transparent;\n  border-left: 0;\n  border-right: 4px solid #f7f7f7;\n  transform: translate(calc(-100%), -50%);\n}\n/* Default top arrow for tooltip */\n.cg-tooltip-arrow-top::before {\n  content: '';\n  position: absolute;\n  display: block;\n  width: 0px;\n  left: 50%;\n  top: 0;\n  border: 5px solid transparent;\n  border-top: 0;\n  border-bottom: 5px solid #938e8e;\n  transform: translate(-50%, -100%);\n}\n.cg-tooltip-arrow-top::after {\n  content: '';\n  position: absolute;\n  display: block;\n  width: 0px;\n  left: 50%;\n  top: 0;\n  border: 4px solid transparent;\n  border-top: 0;\n  border-bottom: 4px solid #f7f7f7;\n  transform: translate(-50%, -100%);\n}\n/* Default right arrow for tooltip */\n.cg-tooltip-arrow-right::before {\n  content: '';\n  position: absolute;\n  display: block;\n  width: 0px;\n  right: 0;\n  top: 50%;\n  border: 5px solid transparent;\n  border-right: 0;\n  border-left: 5px solid #938e8e;\n  transform: translate(100%, -50%);\n}\n.cg-tooltip-arrow-right::after {\n  content: '';\n  position: absolute;\n  display: block;\n  width: 0px;\n  right: 0;\n  top: 50%;\n  border: 4px solid transparent;\n  border-right: 0;\n  border-left: 4px solid #f7f7f7;\n  transform: translate(100%, -50%);\n}\n/* Default bottom arrow for tooltip */\n.cg-tooltip-arrow-bottom::before {\n  content: '';\n  position: absolute;\n  display: block;\n  width: 0px;\n  left: 50%;\n  bottom: 0px;\n  border: 5px solid transparent;\n  border-bottom: 0;\n  border-top: 5px solid #938e8e;\n  transform: translate(-50%, 100%);\n  z-index: 10000;\n}\n.cg-tooltip-arrow-bottom::after {\n  content: '';\n  position: absolute;\n  display: block;\n  width: 0px;\n  left: 50%;\n  bottom: 0;\n  border: 4px solid transparent;\n  border-bottom: 0;\n  border-top: 4px solid #f7f7f7;\n  transform: translate(-50%, 100%);\n  z-index: 10000;\n}\n/* Tooltip item name */\n/* Tooltip item value */\n/* .cg-tooltip-item-value */\n/* Tooltip item value */\n.cg-tooltip-item-unit {\n  text-transform: none;\n}\n\n.cg-tooltip-item-name {\n    text-transform: capitalize;\n    white-space: nowrap;\n    vertical-align: middle;\n    font-size: 13px;\n}\n.cg-tooltip-row {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  white-space: pre-wrap;\n  font-size: 12px;\n  line-height: 100%;\n  margin: 1px 0;\n}\n.cg-tooltip-title {\n  font-size: 13px;\n  height: 14px;\n  text-transform: capitalize;\n}\n.cg-tooltip-title .cg-tooltip-symbol {\n  margin-right: 0 !important;\n}\n.cg-tooltip-title-name {\n  vertical-align: middle;\n}\n.cg-tooltip-row + .cg-tooltip-row {\n  margin-top: 4px;\n}\n.cg-tooltip-row.cg-tooltip-title + .cg-tooltip-row {\n  margin-top: 5px;\n}\n.cg-tooltip-item-value + .cg-tooltip-item-unit {\n    margin-left: 1px;\n}\n/* Tooltip symbol */\n.cg-tooltip-symbol-cell {\n  display: inline-flex;\n  min-width: 13px; /* 10px size + 3px margin */\n}\n.cg-tooltip-symbol {\n  margin-right: 3px;\n  background-color: transparent;\n  display: block;\n}\n.cg-tooltip-symbol > img {\n  display: block;\n}\n.cg-tooltip-list-cell {\n  display: inline-flex;\n}\n.cg-tooltip-list-symbol {\n  display: block;\n  margin-right: 3px;\n  width: 6px;\n  height: 6px;\n  vertical-align: middle;\n  border-radius: 50%;\n  border: 1px solid rgba(0,0,0,.4);\n}\n.cg-tooltip-symbol-legacy {\n  border-radius: 4px;\n  margin-right: 5px;\n  height: 8px;\n  width: 8px;\n  display: inline-block;\n}\n.cg-tooltip-title-legacy {\n  font-weight: 900;\n}\n\n/* Tooltip symbol circle */\n.cg-tooltip-symbol.circle {\n  height: 10px;\n  width: 10px;\n  display: inline-block;\n  border-radius: 50%;\n  border: 1px solid rgba(0,0,0,.4);\n}\n/* Tooltip symbol line */\n.cg-tooltip-symbol.line {\n    height: 10px;\n    width: 10px;\n    display: inline-block;\n    transform: scale(1.2, 0.2);\n}\n/* Tooltip symbol diamond */\n.cg-tooltip-symbol.diamond {\n    height: 10px;\n    width: 10px;\n    display: inline-block;\n    transform: rotate(45deg);\n    border-radius: 0px;\n}\n/* Tooltip symbol square */\n.cg-tooltip-symbol.square {\n    height: 10px;\n    width: 10px;\n    display: inline-block;\n    border-radius: 0px;\n    border: 1px solid rgba(0,0,0,.4);\n}\n\n","lang":"css"},"children":[]}]}]},{"$$mdtype":"Tag","name":"iframe","attributes":{"src":"https://dc2-documentation.s3.amazonaws.com/documentation/slb-docs-test/5.0/examples/vue/tutorials/index.html#/Seismic/Readers/CustomRemoteReader/customRemoteReader?section=Result&extract=true","width":"100%","height":"488.5px","style":{"border":"none"}},"children":[]}]},"headings":[{"value":"Custom Remote Reader","id":"custom-remote-reader","depth":1},{"value":"Create a Reader","id":"create-a-reader","depth":3},{"value":"Display Data","id":"display-data","depth":3},{"value":"Create a Custom Protocol","id":"create-a-custom-protocol","depth":3},{"value":"Server Implementation","id":"server-implementation","depth":3},{"value":"Routes","id":"routes","depth":3},{"value":"Result","id":"result","depth":3}],"frontmatter":{"title":"Custom Remote Reader","seo":{"title":"Custom Remote Reader"}},"lastModified":"2026-02-11T19:54:32.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/solutions/geotoolkit/tutorials/seismic/readers/custom-remote-reader","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}