Commit 03149702 authored by Hanadi's avatar Hanadi
Browse files

Initial Commit

parents
# Created by https://www.gitignore.io
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Node ###
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# Traffic History Job
## Requirements
* `node: ^14.8.0`
* `npm: ^6.14.8`
## Configuration
1. Run FROST Server making sure that it uses String Ids, see [`docker-compose.example.yaml`](docker-compose.example.yaml).
2. Export Here API Key `HERE_API_KEY` and FROST server url `FROST_URL` (with version postfix) as environment variables, e.g.
```shell
export HERE_API_KEY=yYTMvf0L2oXCvETw6FLzNnr-GjfEd_TO6Fv_YG-v7ps
export FROST_URL=http://localhost:8080/FROST-Server/v1.1
```
3. Install dependencies:
```shell
npm install
```
3. For development, run `npm start` to start a `ts-node` server.
4. For production:
```shell
npm run build
node build/index.js
```
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = __importDefault(require("express"));
const node_cron_1 = __importDefault(require("node-cron"));
const HereClient_1 = require("./src/here/HereClient");
const FrostClient_1 = require("./src/frost/FrostClient");
const logger_1 = require("./src/logger/logger");
const app = express_1.default();
const PORT = 8089;
const geoProximity = {
latitude: 48.791348,
longitude: 9.190342,
distance: 3000
};
if (!process.env.HERE_API_KEY) {
throw new Error("HERE_API_KEY environment variable should be present!");
}
if (!process.env.FROST_URL) {
throw new Error("FROST_URL environment variable should be present!");
}
const hereClient = new HereClient_1.HereClient(process.env.HERE_API_KEY);
const frostClient = new FrostClient_1.FrostClient(process.env.FROST_URL);
function triggerJob(forceUpdateLocations = false) {
return __awaiter(this, void 0, void 0, function* () {
const now = new Date().getTime();
return hereClient.flow(geoProximity)
.then(flowResponse => frostClient.mapAndInsertHereFlowResponse(flowResponse, forceUpdateLocations))
.then(insertedObservations => ({
status: 200,
success: true,
millis: new Date().getTime() - now,
insertedObservations
}))
.catch((err) => {
logger_1.error(err.message, err);
return Object.assign(Object.assign({}, err), { insertedObservations: "Unknown", status: 500, success: false, message: err.message, name: err.name, stack: err.stack, millis: new Date().getTime() - now });
});
});
}
// Routes
app.get("/", (req, res) => res.send("Healthy!"));
app.get("/here", (req, res) => {
hereClient.flow(geoProximity)
.then(body => res.send(body))
.catch(err => console.error(err));
});
app.get("/trigger-job", (req, res) => {
logger_1.info("Received trigger-job request");
const forceUpdateLocations = !!req.query.forceUpdateLocations;
triggerJob(forceUpdateLocations).then(result => {
res.status(result.status).send(result);
logger_1.info(`trigger-job request completed after ${result.millis} ms with status ${result.status}.`);
});
});
node_cron_1.default.schedule("*/5 * * * *", () => {
logger_1.info("Triggering job");
triggerJob().then(result => {
if (result.success)
logger_1.info(`Job executed after ${result.millis} ms. Inserted observations: ${result.insertedObservations}.`);
else
logger_1.info(`Job execution failed after ${result.millis} ms.`);
});
});
// Start Application
app.listen(PORT, () => {
logger_1.info(`⚡️[server]: Server is running at http://localhost:${PORT}`);
});
//# sourceMappingURL=index.js.map
\ No newline at end of file
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=DataTypes.js.map
\ No newline at end of file
{"version":3,"file":"DataTypes.js","sourceRoot":"","sources":["../../../src/frost/DataTypes.ts"],"names":[],"mappings":""}
\ No newline at end of file
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FrostClient = void 0;
const node_fetch_1 = __importDefault(require("node-fetch"));
const logger_1 = require("../logger/logger");
const THINGS_PATH = "Things";
const LOCATIONS_PATH = "Locations";
const FEATURES_OF_INTEREST_PATH = "FeaturesOfInterest";
const DATASTREAMS_PATH = "Datastreams";
const OBSERVATIONS_PATH = "Observations";
const SENSORS_PATH = "Sensors";
const OBSERVED_PROPERTIES_PATH = "ObservedProperties";
const getPathFromType = (type) => {
switch (type) {
case "thing":
return THINGS_PATH;
case "location":
return LOCATIONS_PATH;
case "featureOfInterest":
return FEATURES_OF_INTEREST_PATH;
case "datastream":
return DATASTREAMS_PATH;
case "observation":
return OBSERVATIONS_PATH;
case "sensor":
return SENSORS_PATH;
case "observedProperty":
return OBSERVED_PROPERTIES_PATH;
}
throw new Error(`Unknown type ${type}!`);
};
const DEFAULT_PROJECTION = ["@iot.id"];
class FrostClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
// Initialize Here sensor and Speed observed property if they are not already inserted
this.insertItemIfNotExists("sensor", {
"@iot.id": "here",
name: "Here",
description: "Here - Traffic Flow API",
encodingType: "text/html",
metadata: "https://developer.here.com/documentation/traffic/dev_guide/topics_v6.1/example-flow.html"
})
.then(inserted => {
if (inserted)
console.log("Here sensor was inserted.");
})
.catch(err => console.error(err));
this.insertItemIfNotExists("observedProperty", {
"@iot.id": "speed",
name: "Speed",
definition: "http://www.qudt.org/qudt/owl/1.0.0/quantity/Instances.html#Speed",
description: "The Speed."
})
.then(inserted => {
if (inserted)
console.log("Speed observed property was inserted.");
})
.catch(err => console.error(err));
}
/**
* Returns the first thing with the specified id, or null if not found.
* @param id
* @param projection
*/
getSingleThingById(id, projection) {
return __awaiter(this, void 0, void 0, function* () {
return this.getSingleItemById("thing", id, projection);
});
}
/**
* Returns the first location with the specified id, or null if not found.
* @param id
* @param projection
*/
getSingleLocationById(id, projection) {
return __awaiter(this, void 0, void 0, function* () {
return this.getSingleItemById("location", id, projection);
});
}
/**
* Returns the first feature of interest with the specified id, or null if not found.
* @param id
* @param projection
*/
getSingleFeatureOfInterestById(id, projection) {
return __awaiter(this, void 0, void 0, function* () {
return this.getSingleItemById("featureOfInterest", id, projection);
});
}
getSingleDatastreamById(id, projection) {
return __awaiter(this, void 0, void 0, function* () {
return this.getSingleItemById("datastream", id, projection);
});
}
/**
* Returns the number of inserted observations
* @param hereFlow
* @param forceUpdateLocations
*/
mapAndInsertHereFlowResponse(hereFlow, forceUpdateLocations = false) {
return __awaiter(this, void 0, void 0, function* () {
const data = hereFlow.RWS
.flatMap(rws => rws.RW)
.flatMap(rw => {
const timeStamp = rw.PBT;
const locationsAndResults = rw.FIS
.flatMap(fis => fis.FI)
.map(fi => {
var _a, _b, _c;
const thingId = `${rw.LI}-${fi.TMC.DE}-${fi.TMC.PC}`;
const thingDescription = `${rw.DE} direction to ${fi.TMC.DE}`;
const thing = {
"@iot.id": thingId,
name: thingId,
description: thingDescription
};
const location = {
"@iot.id": thingId,
name: thingId,
description: thingDescription,
encodingType: "application/vnd.geo+json",
location: {
type: "MultiLineString",
coordinates: fi.SHP
.map(v => v.value.toString())
.map(str => str
.trim()
.split(" ")
.map(coord => coord.split(",").reverse().map(x => +x)))
},
properties: {
length: fi.TMC.LE //In Kilometers
},
Things: [{
"@iot.id": thingId
}],
};
const featureOfInterest = {
"@iot.id": location["@iot.id"],
name: location.name,
description: location.description,
encodingType: location.encodingType,
feature: location.location,
properties: location.properties,
};
const speed = (_a = fi.CF[0]) === null || _a === void 0 ? void 0 : _a.SP;
const confidence = (_b = fi.CF[0]) === null || _b === void 0 ? void 0 : _b.CN;
const jamFactor = (_c = fi.CF[0]) === null || _c === void 0 ? void 0 : _c.JF;
return { thing, location, featureOfInterest, timeStamp, speed, confidence, jamFactor };
})
.filter(data => !!data.speed); // if failed to obtain speed data, don't consider it
/***** Log if ID repetitions Found *****/
Object.entries(locationsAndResults.map(data => data.featureOfInterest["@iot.id"])
.reduce(function (prev, cur) {
prev[cur] = (prev[cur] || 0) + 1;
return prev;
}, {}))
.forEach(([foiId, repetitions]) => {
if (repetitions > 1) {
console.error(`FeaturesOfInterest: ${foiId} is repeated ${repetitions} times`);
}
});
/***** *************************** *****/
return locationsAndResults;
});
return Promise.all(data.map(roadwayData => {
const datastreamId = `here-${roadwayData.thing["@iot.id"]}`;
return this.insertItemIfNotExists("thing", roadwayData.thing)
.then(() => {
const datastream = {
"@iot.id": datastreamId,
name: datastreamId,
description: `Speed readings from Here for roadway ${roadwayData.thing.description}`,
observationType: "http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement",
unitOfMeasurement: {
name: "Kilometers per hour",
symbol: "km/h",
definition: "http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#KilometerPerHour"
},
ObservedProperty: {
"@iot.id": "speed"
},
Sensor: {
"@iot.id": "here"
},
Thing: {
"@iot.id": roadwayData.thing["@iot.id"]
},
};
return this.insertItemIfNotExists("datastream", datastream);
})
.then(() => this.insertItemIfNotExists("location", roadwayData.location, forceUpdateLocations))
.then(() => this.insertItemIfNotExists("featureOfInterest", roadwayData.featureOfInterest, forceUpdateLocations))
.then(() => {
const observation = {
phenomenonTime: roadwayData.timeStamp,
result: roadwayData.speed,
resultQuality: roadwayData.confidence,
resultTime: roadwayData.timeStamp,
FeatureOfInterest: {
"@iot.id": roadwayData.featureOfInterest["@iot.id"]
},
Datastream: {
"@iot.id": datastreamId
},
parameters: {
jamFactor: roadwayData.jamFactor
}
};
return this.insertItem("observation", observation);
})
.then(() => 1);
})).then(length => length.reduce((a, b) => a + b, 0));
});
}
insertItemIfNotExists(type, frostItemWithId, forceUpdate = false) {
return __awaiter(this, void 0, void 0, function* () {
const existingThing = yield this.getSingleItemById(type, frostItemWithId["@iot.id"]);
if (existingThing && forceUpdate) {
logger_1.info(`Force updating item ${frostItemWithId["@iot.id"]} with type ${type}`);
return this.updateItem(type, frostItemWithId);
}
if (existingThing)
return false;
return this.insertItem(type, frostItemWithId);
});
}
insertItem(type, frostItem) {
return __awaiter(this, void 0, void 0, function* () {
// if (type == "location")
// console.debug(`inserting item with type ${type}`);
const path = getPathFromType(type);
let url = `${this.baseUrl}/${path}`;
return node_fetch_1.default(url, {
method: "POST",
body: JSON.stringify(frostItem)
})
.then(res => {
if (!res.ok) {
console.error(`Error inserting item ${JSON.stringify(frostItem)} with type ${type}`);
res.text().then(text => console.error(text));
const err = new Error(`Error inserting item ${JSON.stringify(frostItem)} with type ${type}`);
err.name = "COULD_NOT_INSERT_ITEM";
throw err;
}
return res.ok;
});
});
}
updateItem(type, frostItem) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
// if (type == "location")
// console.debug(`inserting item with type ${type}`);
const path = getPathFromType(type);
const encodedId = (_a = frostItem["@iot.id"]) === null || _a === void 0 ? void 0 : _a.split("/").map(x => encodeURIComponent(x)).join("/");
let url = `${this.baseUrl}/${path}('${encodedId}')`;
return node_fetch_1.default(url, {
method: "PATCH",
body: JSON.stringify(frostItem)
})
.then(res => {
if (!res.ok) {
console.error(`Error updating item ${JSON.stringify(frostItem)} with type ${type}`);
res.text().then(text => console.error(text));
const err = new Error(`Error inserting item ${JSON.stringify(frostItem)} with type ${type}`);
err.name = "COULD_NOT_INSERT_ITEM";
throw err;
}
return res.ok;
});
});
}
getSingleItemById(type, id, projection = DEFAULT_PROJECTION) {
return __awaiter(this, void 0, void 0, function* () {
const path = getPathFromType(type);
const params = Object.assign({ "$filter": `id eq '${id}'`, "$top": 1 }, (projection ? { "$select": projection === null || projection === void 0 ? void 0 : projection.join(",") } : {}));
return this.get(path, params).then(res => res.value[0]);
});
}
get(urlPath, params) {
return __awaiter(this, void 0, void 0, function* () {
let url = `${this.baseUrl}/${urlPath}`;
if (!params)
params = {};
if (Object.keys(params)) {
url += "?";
url += Object.entries(params)
.map(([key, value]) => [key, encodeURIComponent(value)])
.map(([key, value]) => `${key}=${value}`)
.join("&");
}
return node_fetch_1.default(url)
.then(res => res.json())
.then(json => {
// console.debug(`Getting url "${url}"`);
// console.debug(json);
return json;
});
});
}
}
exports.FrostClient = FrostClient;
//# sourceMappingURL=FrostClient.js.map
\ No newline at end of file
{"version":3,"file":"FrostClient.js","sourceRoot":"","sources":["../../../src/frost/FrostClient.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=DataTypes.js.map
\ No newline at end of file
{"version":3,"file":"DataTypes.js","sourceRoot":"","sources":["../../../src/here/DataTypes.ts"],"names":[],"mappings":""}
\ No newline at end of file
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HereClient = void 0;
const node_fetch_1 = __importDefault(require("node-fetch"));
const BASE_URL = "https://traffic.ls.hereapi.com";
const TRAFFIC_PATH = "traffic/6.1";
class HereClient {
constructor(apiKey) {
this.apiKey = apiKey;
}
flow(position, units = "metric") {
return __awaiter(this, void 0, void 0, function* () {
const prox = `${position.latitude},${position.longitude},${position.distance}`;
const urlPath = `${TRAFFIC_PATH}/flow.json`;
return this.get(urlPath, { prox, units });
});
}
get(urlPath, params) {
return __awaiter(this, void 0, void 0, function* () {
let url = `${BASE_URL}/${urlPath}`;
if (!params)
params = {};
params.apiKey = this.apiKey;
params.responseattributes = "shape";
url += "?";
url += Object.entries(params)
.map(([key, value]) => `${key}=${value}`)
.join("&");
return node_fetch_1.default(url).then(res => res.json());
});
}
}
exports.HereClient = HereClient;
//# sourceMappingURL=HereClient.js.map
\ No newline at end of file
{"version":3,"file":"HereClient.js","sourceRoot":"","sources":["../../../src/here/HereClient.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,4DAA+B;AAG/B,MAAM,QAAQ,GAAG,gCAAgC,CAAC;AAClD,MAAM,YAAY,GAAG,aAAa,CAAC;AAEnC,MAAa,UAAU;IACnB,YACY,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IACvB,CAAC;IAEE,IAAI,CAAC,QAAsB,EAAE,QAAmB,QAAQ;;YAC1D,MAAM,IAAI,GAAG,GAAG,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC/E,MAAM,OAAO,GAAG,GAAG,YAAY,YAAY,CAAC;YAC5C,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,EAAC,IAAI,EAAE,KAAK,EAAC,CAAC,CAAC;QAC5C,CAAC;KAAA;IAEa,GAAG,CAAC,OAAe,EAAE,MAAiC;;YAChE,IAAI,GAAG,GAAG,GAAG,QAAQ,IAAI,OAAO,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM;gBACP,MAAM,GAAG,EAAE,CAAC;YAChB,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YAC5B,MAAM,CAAC,kBAAkB,GAAG,OAAO,CAAC;YACpC,GAAG,IAAI,GAAG,CAAC;YACX,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;iBACxB,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;iBACxC,IAAI,CAAC,GAAG,CAAC,CAAC;YACf,OAAO,oBAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;KAAA;CACJ;AAvBD,gCAuBC"}
\ No newline at end of file
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.debug = exports.error = exports.info = void 0;
const log = (text, logType) => {
const time = new Date().toISOString();
console.log(`${time} ${logType} ${text}`);
};
const info = (text) => log(text, "INFO");
exports.info = info;
const error = (text, err) => {
log(text, "ERROR");
if (err)
console.error(err);
};
exports.error = error;
const debug = (text) => log(text, "DEBUG");
exports.debug = debug;
//# sourceMappingURL=logger.js.map
\ No newline at end of file
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/logger/logger.ts"],"names":[],"mappings":";;;AAAA,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,OAAmC,EAAE,EAAE;IAC9D,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC,CAAC;AAC9C,CAAC,CAAC;AACK,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAA3C,QAAA,IAAI,QAAuC;AACjD,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,GAAW,EAAE,EAAE;IAC/C,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnB,IAAI,GAAG;QACH,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC,CAAC;AAJW,QAAA,KAAK,SAIhB;AACK,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAA7C,QAAA,KAAK,SAAwC"}
\ No newline at end of file
version: '3.1'
services:
web:
image: fraunhoferiosb/frost-server:latest
environment:
- serviceRootUrl=http://localhost:8080/FROST-Server
- http_cors_enable=true
- http_cors_allowed.origins=*
- mqtt.WebsocketPort=9876
- persistence_db_driver=org.postgresql.Driver
- persistence_db_url=jdbc:postgresql://database:5432/sensorThing
- persistence_db_username=sensorThing
- persistence_db_password=sensorThing
- persistence_autoUpdateDatabase=true
- plugins.openApi.enable=true
- persistence.idGenerationMode=ServerAndClientGenerated
- persistence.persistenceManagerImplementationClass=de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.imp.PostgresPersistenceManagerString
ports:
- 8080:8080
- 1883:1883
- 9876:9876
depends_on:
- database_setup
database:
image: postgis/postgis:11-2.5-alpine
ports:
- 5432:5432
environment:
- POSTGRES_DB=sensorThing
- POSTGRES_USER=sensorThing
- POSTGRES_PASSWORD=sensorThing
volumes:
- postgis_volume:/var/lib/postgresql/data
database_setup:
image: postgis/postgis:11-2.5-alpine
environment:
- PGPASSWORD=sensorThing
command:
- /bin/bash
- -c
- |
sleep 5
echo 'CREATE EXTENSION "uuid-ossp";' | psql -h database -p 5432 -U sensorThing
links:
- database
volumes:
postgis_volume:
import express from "express";
import cron from "node-cron";
import {HereClient} from "./src/here/HereClient";
import {FrostClient} from "./src/frost/FrostClient";
import {GeoProximity} from "./src/here/DataTypes";
import {error, info} from "./src/logger/logger";
const app = express();
const PORT = 8089;
const geoProximity: GeoProximity = {
latitude: 48.791348,
longitude: 9.190342,
distance: 3000
};
if (!process.env.HERE_API_KEY) {
throw new Error("HERE_API_KEY environment variable should be present!");
}
if (!process.env.FROST_URL) {
throw new Error("FROST_URL environment variable should be present!");
}
const hereClient = new HereClient(process.env.HERE_API_KEY);
const frostClient = new FrostClient(process.env.FROST_URL);
async function triggerJob(forceUpdateLocations: boolean = false) {
const now = new Date().getTime();
return hereClient.flow(geoProximity)
.then(flowResponse => frostClient.mapAndInsertHereFlowResponse(flowResponse, forceUpdateLocations))
.then(insertedObservations => ({
status: 200,
success: true,
millis: new Date().getTime() - now,
insertedObservations
}))
.catch((err: Error) => {
error(err.message, err);
return {
...err,
insertedObservations: "Unknown",
status: 500,
success: false,
message: err.message,
name: err.name,
stack: err.stack,
millis: new Date().getTime() - now
};
});
}
// Routes
app.get("/", (req, res) => res.send("Healthy!"));
app.get("/here", (req, res) => {
hereClient.flow(geoProximity)
.then(body => res.send(body))
.catch(err => console.error(err));
});
app.get("/trigger-job", (req, res) => {
info("Received trigger-job request");
const forceUpdateLocations = !!req.query.forceUpdateLocations
triggerJob(forceUpdateLocations).then(result => {
res.status(result.status).send(result);
info(`trigger-job request completed after ${result.millis} ms with status ${result.status}.`);
});
});
cron.schedule("*/5 * * * *", () => {
info("Triggering job");
triggerJob().then(result => {
if (result.success)
info(`Job executed after ${result.millis} ms. Inserted observations: ${result.insertedObservations}.`);
else
info(`Job execution failed after ${result.millis} ms.`);
});
});
// Start Application
app.listen(PORT, () => {
info(`⚡️[server]: Server is running at http://localhost:${PORT}`);
});
This diff is collapsed.
{
"name": "traffic-history-job",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon index.ts",
"build": "tsc --project ./",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://gitlab.com/hanadiEbrahim/traffic-history-job.git"
},
"author": "Hanadi Ebrahim",
"license": "ISC",
"bugs": {
"url": "https://gitlab.com/hanadiEbrahim/traffic-history-job/issues"
},
"homepage": "https://gitlab.com/hanadiEbrahim/traffic-history-job#readme",
"dependencies": {
"express": "^4.17.1",
"node-cron": "^2.0.3",
"node-fetch": "^2.6.1"
},
"devDependencies": {
"@types/node-cron": "^2.0.3",
"@types/node-fetch": "^2.5.7",
"@types/express": "^4.17.9",
"@types/node": "^14.14.14",
"nodemon": "^2.0.6",
"ts-node": "^9.1.1",
"typescript": "^4.1.3"
}
}
export type FrostProjection = string[];
export type FrostBaseEntity = {
"@iot.id"?: string;
[key: string]: any;
}
export type FrostThing = FrostBaseEntity & {
name?: string;
description?: string;
properties?: any;
}
export type GeoLocation = any;
export type FrostLocation = FrostBaseEntity & {
name?: string;
description?: string;
encodingType?: string;
location?: GeoLocation;
properties?: any;
}
export type FrostFeatureOfInterest = FrostBaseEntity & {
name?: string;
description?: string;
encodingType?: string;
feature?: GeoLocation;
properties?: any;
}
export type FrostDatastreamInput = FrostBaseEntity & {
name: string;
description: string;
observationType: string;
unitOfMeasurement: {
name: string;
symbol: string;
definition: string;
};
ObservedProperty: FrostBaseEntity;
Sensor: FrostBaseEntity;
Thing: FrostBaseEntity;
}
export type FrostObservationInput = FrostBaseEntity & {
phenomenonTime: string;
result: number;
resultQuality: number;
resultTime: string;
FeatureOfInterest: FrostBaseEntity;
parameters?: any;
Datastream: FrostBaseEntity;
}
import {
FrostBaseEntity,
FrostDatastreamInput,
FrostFeatureOfInterest,
FrostLocation,
FrostObservationInput,
FrostProjection,
FrostThing
} from "./DataTypes";
import fetch from "node-fetch";
import {FlowResponse} from "../here/DataTypes";
import {info} from "../logger/logger";
const THINGS_PATH = "Things";
const LOCATIONS_PATH = "Locations";
const FEATURES_OF_INTEREST_PATH = "FeaturesOfInterest";
const DATASTREAMS_PATH = "Datastreams";
const OBSERVATIONS_PATH = "Observations";
const SENSORS_PATH = "Sensors";
const OBSERVED_PROPERTIES_PATH = "ObservedProperties";
type ItemType = "location" | "thing" | "featureOfInterest" | "datastream" | "observation" | "sensor" | "observedProperty";
const getPathFromType = (type: ItemType) => {
switch (type) {
case "thing":
return THINGS_PATH;
case "location":
return LOCATIONS_PATH;
case "featureOfInterest":
return FEATURES_OF_INTEREST_PATH;
case "datastream":
return DATASTREAMS_PATH;
case "observation":
return OBSERVATIONS_PATH;
case "sensor":
return SENSORS_PATH;
case "observedProperty":
return OBSERVED_PROPERTIES_PATH;
}
throw new Error(`Unknown type ${type}!`);
};
const DEFAULT_PROJECTION: FrostProjection = ["@iot.id"];
export class FrostClient {
constructor(
private baseUrl: string
) {
// Initialize Here sensor and Speed observed property if they are not already inserted
this.insertItemIfNotExists("sensor", {
"@iot.id": "here",
name: "Here",
description: "Here - Traffic Flow API",
encodingType: "text/html",
metadata: "https://developer.here.com/documentation/traffic/dev_guide/topics_v6.1/example-flow.html"
})
.then(inserted => {
if (inserted) console.log("Here sensor was inserted.");
})
.catch(err => console.error(err));
this.insertItemIfNotExists("observedProperty", {
"@iot.id": "speed",
name: "Speed",
definition: "http://www.qudt.org/qudt/owl/1.0.0/quantity/Instances.html#Speed",
description: "The Speed."
})
.then(inserted => {
if (inserted) console.log("Speed observed property was inserted.");
})
.catch(err => console.error(err));
}
/**
* Returns the first thing with the specified id, or null if not found.
* @param id
* @param projection
*/
async getSingleThingById(id: string, projection?: FrostProjection): Promise<FrostThing> {
return this.getSingleItemById("thing", id, projection);
}
/**
* Returns the first location with the specified id, or null if not found.
* @param id
* @param projection
*/
async getSingleLocationById(id: string, projection?: FrostProjection): Promise<FrostLocation> {
return this.getSingleItemById("location", id, projection);
}
/**
* Returns the first feature of interest with the specified id, or null if not found.
* @param id
* @param projection
*/
async getSingleFeatureOfInterestById(id: string, projection?: FrostProjection): Promise<FrostFeatureOfInterest> {
return this.getSingleItemById("featureOfInterest", id, projection);
}
async getSingleDatastreamById(id: string, projection?: FrostProjection): Promise<any> {
return this.getSingleItemById("datastream", id, projection);
}
/**
* Returns the number of inserted observations
* @param hereFlow
* @param forceUpdateLocations
*/
async mapAndInsertHereFlowResponse(hereFlow: FlowResponse, forceUpdateLocations: boolean = false): Promise<number> {
const data = hereFlow.RWS
.flatMap(rws => rws.RW)
.flatMap(rw => {
const timeStamp = rw.PBT;
const locationsAndResults =
rw.FIS
.flatMap(fis => fis.FI)
.map(fi => {
const thingId = `${rw.LI}-${fi.TMC.DE}-${fi.TMC.PC}`;
const thingDescription = `${rw.DE} direction to ${fi.TMC.DE}`;
const thing: FrostThing = {
"@iot.id": thingId,
name: thingId,
description: thingDescription
};
const location: FrostLocation = {
"@iot.id": thingId,
name: thingId,
description: thingDescription,
encodingType: "application/vnd.geo+json",
location: {
type: "MultiLineString",
coordinates: fi.SHP!
.map(v => v.value.toString())
.map(
str =>
str
.trim()
.split(" ")
.map(coord => coord.split(",").reverse().map(x => +x))
)
},
properties: {
length: fi.TMC.LE //In Kilometers
},
Things: [{
"@iot.id": thingId
}],
};
const featureOfInterest: FrostFeatureOfInterest = {
"@iot.id": location["@iot.id"],
name: location.name,
description: location.description,
encodingType: location.encodingType,
feature: location.location,
properties: location.properties,
};
const speed = fi.CF[0]?.SP;
const confidence = fi.CF[0]?.CN;
const jamFactor = fi.CF[0]?.JF;
return {thing, location, featureOfInterest, timeStamp, speed, confidence, jamFactor};
})
.filter(data => !!data.speed); // if failed to obtain speed data, don't consider it
/***** Log if ID repetitions Found *****/
Object.entries(locationsAndResults.map(data => data.featureOfInterest["@iot.id"]!)
.reduce(function (prev: { [key: string]: number }, cur: string) {
prev[cur] = (prev[cur] || 0) + 1;
return prev;
}, {}))
.forEach(([foiId, repetitions]) => {
if (repetitions > 1) {
console.error(`FeaturesOfInterest: ${foiId} is repeated ${repetitions} times`);
}
});
/***** *************************** *****/
return locationsAndResults;
});
return Promise.all(data.map(roadwayData => {
const datastreamId = `here-${roadwayData.thing["@iot.id"]}`;
return this.insertItemIfNotExists("thing", roadwayData.thing)
.then(() => {
const datastream: FrostDatastreamInput = {
"@iot.id": datastreamId,
name: datastreamId,
description: `Speed readings from Here for roadway ${roadwayData.thing.description}`,
observationType: "http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement",
unitOfMeasurement: {
name: "Kilometers per hour",
symbol: "km/h",
definition: "http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#KilometerPerHour"
},
ObservedProperty: {
"@iot.id": "speed"
},
Sensor: {
"@iot.id": "here"
},
Thing: {
"@iot.id": roadwayData.thing["@iot.id"]
},
};
return this.insertItemIfNotExists("datastream", datastream);
})
.then(() => this.insertItemIfNotExists("location", roadwayData.location, forceUpdateLocations))
.then(() => this.insertItemIfNotExists("featureOfInterest", roadwayData.featureOfInterest, forceUpdateLocations))
.then(() => {
const observation: FrostObservationInput = {
phenomenonTime: roadwayData.timeStamp,
result: roadwayData.speed,
resultQuality: roadwayData.confidence,
resultTime: roadwayData.timeStamp,
FeatureOfInterest: {
"@iot.id": roadwayData.featureOfInterest["@iot.id"]
},
Datastream: {
"@iot.id": datastreamId
},
parameters: {
jamFactor: roadwayData.jamFactor
}
};
return this.insertItem("observation", observation);
})
.then(() => 1);
})).then(length => length.reduce((a, b) => a + b, 0));
}
private async insertItemIfNotExists(
type: ItemType,
frostItemWithId: FrostBaseEntity,
forceUpdate: boolean = false
): Promise<boolean> {
const existingThing = await this.getSingleItemById(type, frostItemWithId["@iot.id"]!);
if (existingThing && forceUpdate) {
info(`Force updating item ${frostItemWithId["@iot.id"]} with type ${type}`);
return this.updateItem(type, frostItemWithId);
}
if (existingThing)
return false;
return this.insertItem(type, frostItemWithId);
}
private async insertItem(
type: ItemType,
frostItem: FrostBaseEntity
): Promise<boolean> {
// if (type == "location")
// console.debug(`inserting item with type ${type}`);
const path = getPathFromType(type);
let url = `${this.baseUrl}/${path}`;
return fetch(url, {
method: "POST",
body: JSON.stringify(frostItem)
})
.then(res => {
if (!res.ok) {
console.error(`Error inserting item ${JSON.stringify(frostItem)} with type ${type}`);
res.text().then(text => console.error(text));
const err = new Error(`Error inserting item ${JSON.stringify(frostItem)} with type ${type}`);
err.name = "COULD_NOT_INSERT_ITEM";
throw err;
}
return res.ok;
});
}
private async updateItem(
type: ItemType,
frostItem: FrostBaseEntity
): Promise<boolean> {
// if (type == "location")
// console.debug(`inserting item with type ${type}`);
const path = getPathFromType(type);
const encodedId = frostItem["@iot.id"]?.split("/").map(x => encodeURIComponent(x)).join("/");
let url = `${this.baseUrl}/${path}('${encodedId}')`;
return fetch(url, {
method: "PATCH",
body: JSON.stringify(frostItem)
})
.then(res => {
if (!res.ok) {
console.error(`Error updating item ${JSON.stringify(frostItem)} with type ${type}`);
res.text().then(text => console.error(text));
const err = new Error(`Error inserting item ${JSON.stringify(frostItem)} with type ${type}`);
err.name = "COULD_NOT_INSERT_ITEM";
throw err;
}
return res.ok;
});
}
private async getSingleItemById(
type: ItemType,
id: string,
projection: FrostProjection = DEFAULT_PROJECTION
): Promise<FrostBaseEntity> {
const path = getPathFromType(type);
const params = {
"$filter": `id eq '${id}'`,
"$top": 1,
...(projection ? {"$select": projection?.join(",")} : {})
};
return this.get(path, params).then(res => res.value[0]);
}
private async get(urlPath: string, params?: { [key: string]: string | number }): Promise<any> {
let url = `${this.baseUrl}/${urlPath}`;
if (!params)
params = {};
if (Object.keys(params)) {
url += "?";
url += Object.entries(params)
.map(([key, value]) => [key, encodeURIComponent(value)])
.map(([key, value]) => `${key}=${value}`)
.join("&");
}
return fetch(url)
.then(res => res.json())
.then(json => {
// console.debug(`Getting url "${url}"`);
// console.debug(json);
return json;
});
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment