The GeoToolkit doesn't have the standard implementation of middle-tier architecture and the communication between the client and the server solution does not preclude the use of server-side technologies such as PHP, ASP.NET, JSP, Python or others. This tutorial shows how to implement client-server communication to display real-time data.
See the Real-time Data tutorial.
# Show Communication Between Widget and Server
This example contains simple server implementation written on nodejs and client code, which requests real-time as well as historical data from the server. To run application, perform the following steps.
- Install nodejs
- In terminal, cd to server (examples/vue/tutorials/src/code/WellLog/DataAndTemplates/RealTimeServer/realtimedemoserver) folder
- Run npm install
- Run npm start
- Type in your browser http://localhost:3003/to verify that server is running
# HTTP communication
This example shows implementation of real-time data using HTTP requests to the server. The server supports three requests:
- Get limits of data
- Get [real-time data points](http://localhost:3003/logdata?low=1357802231800&high=1357802312000&time=1357802312000&mnemonics %5B%5D=cali&mnemonics%5B%5D=gr&historical=false)
- Get [historical data points](http://localhost:3003/logdata?low=1357799613762.264&high=1357799693962.264&mnemonics %5B%5D=cali&mnemonics%5B%5D=gr&historical=true)
The client data source extends geotoolkit/data/DataSource and requests real time data, sending the last data point received, or historical data, when widget has been scrolled or panned.
import { HeaderType } from "@int/geotoolkit/welllog/header/LogAxisVisualHeader.ts";
import { TrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { Events as AbstractScrollEvents } from "@int/geotoolkit/controls/tools/scroll/AbstractScroll.ts";
import { Events as PanningEvents } from "@int/geotoolkit/controls/tools/Panning.ts";
import { Range } from "@int/geotoolkit/util/Range.ts";
import { Timer } from "@int/geotoolkit/util/Timer.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { LogData } from "@int/geotoolkit/welllog/data/LogData.ts";
import { LogCurve } from "@int/geotoolkit/welllog/LogCurve.ts";
import { KnownColors } from "@int/geotoolkit/util/ColorUtil.ts";
import { RealtimeData } from "/src/code/WellLog/DataAndTemplates/RealTimeServer/common/realtimeDemoServerData.ts";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
const defaults = {
zoomInScale: 5 / 4,
zoomOutScale: 4 / 5
};
const REFRESH_TIME = 1e3;
const INTERVAL = 1e3;
const SCALE_COEFFICIENT = 1 / 200;
const INDEX_TRACK_WIDTH = 60;
const SERVER_HOST = "http://localhost:3003";
class HTTPDemo {
constructor(options) {
this.isConnected = false;
this.plot = null;
this.scaleFactor = 1;
this.timer = null;
this.isRunning = false;
this.onError = options.onerror;
this.widget = this.createWidget();
this.plot = new Plot({
canvaselement: options.canvas,
root: this.widget
});
}
dispose() {
if (this.timer) {
this.timer.stop();
}
if (this.plot) {
this.plot.dispose();
}
}
createWidget() {
const widget = createWellLogWidget({
"indextype": "time",
"indexunit": "ms"
});
widget.setAxisHeaderType(HeaderType.Simple);
this.createTestData((err, source) => {
if (err) {
this.onError(true);
return;
}
const BindingFunction = {
accept: (node) => node instanceof LogCurve,
bind: (curve, data) => {
if (curve.getDataSource() != null) {
curve.getDataSource().clear();
}
if (data != null && data instanceof RealtimeData) {
data.setCurveSource(curve);
}
}
};
widget.getDataBinding().add(BindingFunction);
widget.setData(source);
widget.addTrack(TrackType.IndexTrack).setWidth(INDEX_TRACK_WIDTH);
widget.addTrack(TrackType.LinearTrack).addChild([
this.createCurve("CALI").setLineStyle(KnownColors.Green).setNormalizationLimits(100, 150)
]);
widget.addTrack(TrackType.IndexTrack).setWidth(INDEX_TRACK_WIDTH);
widget.addTrack(TrackType.LogTrack).addChild([
this.createCurve("GR").setLineStyle(KnownColors.Red).setNormalizationLimits(0, 30)
]);
const lowerLimit = widget.getData().getLowerLimit();
widget.setDepthLimits(new Range(lowerLimit, lowerLimit));
widget.setVisibleDepthLimits(new Range(lowerLimit, lowerLimit));
widget.getToolByName("TrackPlotVerticalScroll").on(AbstractScrollEvents.onScrollEnd, this.widgetScrolled.bind(this));
widget.getToolByName("TrackPanning").on(PanningEvents.onPanningEnd, this.widgetScrolled.bind(this));
widget.scale(SCALE_COEFFICIENT);
this.isConnected = true;
this.startRealTime();
});
return widget;
}
widgetScrolled() {
if (this.isRunning) {
this.stopRealTime();
}
const visibleLimits = this.widget.getVisibleDepthLimits();
this.widget.getData().getHistorical(visibleLimits, () => {
});
}
createCurve(mnemonic, source) {
source = source || this.widget.getData();
let d = source.getCurveSource(mnemonic);
if (d == null) {
d = new LogData(mnemonic);
}
const curve = new LogCurve(d, true);
curve.setId(mnemonic);
return curve;
}
createTestData(cb) {
const ds = new RealtimeData();
ds.initialize(INTERVAL, (err, dataSource) => {
if (err) {
return cb(err);
}
this.timer = new Timer(REFRESH_TIME, () => {
const visibleLimits = this.widget.getVisibleDepthLimits();
const depthLimits = this.widget.getDepthLimits();
if (this.isRunning) {
dataSource.getData(depthLimits.getHigh(), visibleLimits, () => {
depthLimits.setRange(depthLimits.getLow(), dataSource.getHigherLimit());
this.widget.setDepthLimits(depthLimits);
this.scrollDownWidget();
});
}
});
return cb(null, ds);
}, SERVER_HOST);
}
scrollDownWidget() {
const limits = this.widget.getDepthLimits();
const high = limits.getHigh();
const vdl = this.widget.getTrackContainer().getVisibleDepthLimits();
if (vdl.getLow() > 0 && vdl.getHigh() > 0) {
this.widget.getTrackContainer().scrollToDepth(high);
}
}
zoomIn() {
this.widget.scale(defaults.zoomInScale);
this.scaleFactor *= defaults.zoomOutScale;
this.scrollDownWidget();
}
zoomOut() {
this.widget.scale(defaults.zoomOutScale);
this.scaleFactor /= defaults.zoomOutScale;
this.scrollDownWidget();
}
zoomReset() {
this.widget.scale(this.scaleFactor);
this.scaleFactor = 1;
this.scrollDownWidget();
}
startRealTime() {
this.timer.start();
this.isRunning = true;
}
stopRealTime() {
this.timer.stop();
this.isRunning = false;
}
toggleRealTime() {
this.isRunning ? this.stopRealTime() : this.startRealTime();
}
isRealTime() {
return this.isRunning;
}
}
export { HTTPDemo };
createScene();
# Socket.IO Communication
This example shows implementation of real time data using Sockets. A socket connection is established with server. The client then listens to an event which sends alerts when new data is available on the server. Whenever client decides that it needs the data, it emits an event requesting for data of a specific range.
Events:
connect: Fired upon a successful connectionget_limits: Fired by client on initialization, request for known limitslimits: Fired by server when known limits are sentdata_range_changed: Fired by server when new data is availableget_real_time_data: Fired by client when data is needed, along with event requested range is sentreal_time_data: Fired by server when data requested is sent
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { HeaderType } from "@int/geotoolkit/welllog/header/LogAxisVisualHeader.ts";
import { TrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { Events as AbstractScrollEvents } from "@int/geotoolkit/controls/tools/scroll/AbstractScroll.ts";
import { Events as PanningEvents } from "@int/geotoolkit/controls/tools/Panning.ts";
import { Range } from "@int/geotoolkit/util/Range.ts";
import { KnownColors } from "@int/geotoolkit/util/ColorUtil.ts";
import { LogData } from "@int/geotoolkit/welllog/data/LogData.ts";
import { LogCurve } from "@int/geotoolkit/welllog/LogCurve.ts";
import { SocketData } from "/src/code/WellLog/DataAndTemplates/RealTimeServer/common/realtimeSocketData.ts";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
const defaults = {
zoomInScale: 5 / 4,
zoomOutScale: 4 / 5
};
const SCALE_COEFFICIENT = 1 / 200;
const INDEX_TRACK_WIDTH = 60;
const SERVER_HOST = "http://localhost:3003";
class SocketDemo {
constructor(options) {
this.scaleFactor = 1;
this.widget = this.createWidget();
this.plot = new Plot({
canvaselement: options.canvas,
root: this.widget
});
}
dispose() {
const data = this.getData();
if (data) {
data.stopRealTime();
}
if (this.plot) {
this.plot.dispose();
}
}
getData() {
return this.widget != null ? this.widget.getData() : null;
}
createWidget() {
const widget = createWellLogWidget({
"indextype": "time",
"indexunit": "ms"
});
widget.setAxisHeaderType(HeaderType.Simple);
const source = new SocketData(widget);
const addDataToWidget = () => {
widget.getDataBinding().add(source.getBindingFunction());
widget.setData(source);
widget.addTrack(TrackType.IndexTrack).setWidth(INDEX_TRACK_WIDTH);
widget.addTrack(TrackType.LinearTrack).addChild([
this.createCurve("CALI", source).setLineStyle(KnownColors.Green).setNormalizationLimits(100, 150)
]);
widget.addTrack(TrackType.IndexTrack).setWidth(INDEX_TRACK_WIDTH);
widget.addTrack(TrackType.LogTrack).addChild([
this.createCurve("GR", source).setLineStyle(KnownColors.Red).setNormalizationLimits(0, 30)
]);
const lowerLimit = widget.getData().getLowerLimit();
widget.setDepthLimits(new Range(lowerLimit, lowerLimit));
widget.setVisibleDepthLimits(new Range(lowerLimit, lowerLimit));
widget.getToolByName("TrackPlotVerticalScroll").on(AbstractScrollEvents.onScrollEnd, this.widgetScrolled.bind(this));
widget.getToolByName("TrackPanning").on(PanningEvents.onPanningEnd, this.widgetScrolled.bind(this));
widget.scale(SCALE_COEFFICIENT);
source.startRealTime();
};
source.initialize(1e3, addDataToWidget, SERVER_HOST);
return widget;
}
createCurve(mnemonic, source) {
source = source || this.getData();
let d = source.getCurveSource(mnemonic);
if (d == null) {
d = new LogData(mnemonic);
}
const curve = new LogCurve(d, true);
curve.setId(mnemonic);
return curve;
}
widgetScrolled() {
this.getData().widgetScrolled();
}
createSocketData() {
const socketData = new SocketData(this.widget);
socketData.initialize(1e3, () => {
}, SERVER_HOST);
}
scrollDownWidget() {
const limits = this.widget.getDepthLimits();
const high = limits.getHigh();
const vdl = this.widget.getTrackContainer().getVisibleDepthLimits();
if (vdl.getLow() > 0 && vdl.getHigh() > 0) {
this.widget.getTrackContainer().scrollToDepth(high);
}
}
zoomIn() {
this.widget.scale(defaults.zoomInScale);
this.scaleFactor *= defaults.zoomOutScale;
this.scrollDownWidget();
}
zoomOut() {
this.widget.scale(defaults.zoomOutScale);
this.scaleFactor /= defaults.zoomOutScale;
this.scrollDownWidget();
}
zoomReset() {
this.widget.scale(this.scaleFactor);
this.scaleFactor = 1;
this.scrollDownWidget();
}
startRealTime() {
this.getData().setRealTime(true).startRealTime();
}
stopRealTime() {
this.getData().setRealTime(false);
}
toggleRealTime() {
this.isRealTime() ? this.stopRealTime() : this.startRealTime();
}
isRealTime() {
return this.getData().isRunning();
}
}
export { SocketDemo };
createScene();
# Log2D Example
This example shows implementation of real time data using HTTP requests to server. The server supports three requests:
- Get limits of data
- Get real-time data points
- Get historical data points
The client data source extends geotoolkit/data/DataSource and requests real time data, sending the last data point received, or historical data, when widget has been scrolled or panned;
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { HeaderType } from "@int/geotoolkit/welllog/header/LogAxisVisualHeader.ts";
import { TrackType } from "@int/geotoolkit/welllog/TrackType.ts";
import { Log2DVisual, PlotTypes } from "@int/geotoolkit/welllog/Log2DVisual.ts";
import { Events as AbstractScrollEvents } from "@int/geotoolkit/controls/tools/scroll/AbstractScroll.ts";
import { Events as PanningEvents } from "@int/geotoolkit/controls/tools/Panning.ts";
import { Range } from "@int/geotoolkit/util/Range.ts";
import { Log2DVisualData } from "@int/geotoolkit/welllog/data/Log2DVisualData.ts";
import { DefaultColorProvider } from "@int/geotoolkit/util/DefaultColorProvider.ts";
import { Timer } from "@int/geotoolkit/util/Timer.ts";
import { KnownColors } from "@int/geotoolkit/util/ColorUtil.ts";
import { Log2DRealTimeData } from "/src/code/WellLog/DataAndTemplates/RealTimeServer/common/log2dRealtimeData.ts";
import { createWellLogWidget } from "/src/code/WellLog/utils/common.ts";
const defaults = {
zoomInScale: 5 / 4,
zoomOutScale: 4 / 5
};
const SCALE_COEFFICIENT = 1 / 200;
const INDEX_TRACK_WIDTH = 60;
const REFRESH_TIME = 1e3;
const INTERVAL = 1e3;
class Log2DDemo {
constructor(options) {
this.isRunning = false;
this.timer = null;
this.scaleFactor = 1;
this.widget = this.createWidget();
this.plot = new Plot({
canvaselement: options.canvas,
root: this.widget
});
}
dispose() {
if (this.timer) {
this.timer.stop();
}
if (this.plot) {
this.plot.dispose();
}
}
createWidget() {
const widget = createWellLogWidget({
"indextype": "time",
"indexunit": "ms"
});
widget.setAxisHeaderType(HeaderType.Simple);
const onCreateLog2D = (err, dataSource) => {
widget.setData(dataSource);
widget.addTrack(TrackType.IndexTrack).setWidth(INDEX_TRACK_WIDTH);
widget.addTrack(TrackType.LinearTrack).addChild([
this.create2DVisual("log1").setPlotType(PlotTypes.Step)
]);
widget.getToolByName("TrackPlotVerticalScroll").on(AbstractScrollEvents.onScrollEnd, this.widgetScrolled.bind(this));
widget.getToolByName("TrackPanning").on(PanningEvents.onPanningEnd, this.widgetScrolled.bind(this));
widget.setDepthLimits(new Range(dataSource.getLowerLimit(), dataSource.getLowerLimit())).setVisibleDepthLimits(new Range(dataSource.getLowerLimit(), dataSource.getLowerLimit() + 1e3)).scale(SCALE_COEFFICIENT);
this.startRealTime();
};
this.createLog2DData(onCreateLog2D);
return widget;
}
create2DVisual(id) {
const visual = new Log2DVisual().setId(id).setData(new Log2DVisualData());
const widgetData = this.widget.getData();
const min = widgetData.getMinValue();
const max = widgetData.getMaxValue();
const delt = (max - min) / 4;
const colors = new DefaultColorProvider().addColor(min, KnownColors.Green).addColor(min + delt, KnownColors.Yellow).addColor(min + 2 * delt, KnownColors.Orange).addColor(max - delt, KnownColors.Blue).addColor(max, KnownColors.Red);
visual.setColorProvider(colors);
this.widget.getData().bindVisual(visual);
return visual;
}
widgetScrolled() {
this.stopRealTime();
this.widget.getData().widgetScrolled(this.widget.getVisibleDepthLimits());
}
scrollDownWidget() {
const limits = this.widget.getDepthLimits();
const high = limits.getHigh();
const vdl = this.widget.getTrackContainer().getVisibleDepthLimits();
if (vdl.getLow() > 0 && vdl.getHigh() > 0) {
this.widget.getTrackContainer().scrollToDepth(high);
}
}
startRealTime() {
this.timer.start();
this.isRunning = true;
}
stopRealTime() {
this.timer.stop();
this.isRunning = false;
}
createLog2DData(cb) {
const ds = new Log2DRealTimeData();
ds.initialize(INTERVAL, (err, dataSource) => {
if (err) {
return cb(err);
}
this.timer = new Timer(REFRESH_TIME, () => {
const visibleLimits = this.widget.getVisibleDepthLimits();
const depthLimits = this.widget.getDepthLimits();
if (this.isRunning) {
dataSource.getData(visibleLimits, depthLimits.getHigh(), () => {
depthLimits.setRange(depthLimits.getLow(), dataSource.getHigherLimit());
visibleLimits.setRange(visibleLimits.getLow(), dataSource.getHigherLimit());
this.widget.setDepthLimits(depthLimits);
this.scrollDownWidget();
});
}
});
return cb(null, dataSource);
});
}
zoomIn() {
this.widget.scale(defaults.zoomInScale);
this.scaleFactor *= defaults.zoomOutScale;
this.scrollDownWidget();
}
zoomOut() {
this.widget.scale(defaults.zoomOutScale);
this.scaleFactor /= defaults.zoomOutScale;
this.scrollDownWidget();
}
zoomReset() {
this.widget.scale(this.scaleFactor);
this.scaleFactor = 1;
this.scrollDownWidget();
}
toggleRealTime() {
this.isRunning ? this.stopRealTime() : this.startRealTime();
}
isRealTime() {
return this.isRunning;
}
}
export { Log2DDemo };
createScene();