var highland = require("highland"); var Promise = require("bluebird"); var Sequelize = require("sequelize"); var underscore = require("underscore"); var elevation = require("../elevation"); var utils = require("../utils"); var routing = require("../routing"); module.exports = function(Database) { Database.prototype._init.push(function() { this._conn.define("Line", { routePoints : { type: Sequelize.TEXT, allowNull: false, get: function() { var routePoints = this.getDataValue("routePoints"); return routePoints != null ? JSON.parse(routePoints) : routePoints; }, set: function(v) { for(var i=0; i { return this.getLinePoints(data.id, bboxWithZoom).then((trackPoints) => { data.trackPoints = trackPoints; return data; }); }); }, getLineTemplate(padId, data) { return utils.promiseAuto({ lineTemplate: () => { var ret = JSON.parse(JSON.stringify(this._conn.model("Line").build(utils.extend({ }, data, { padId: padId })))); ret.data = data.data || { }; return ret; }, type: this.getType(padId, data.typeId), styles: (lineTemplate, type) => { if(type.defaultColour) lineTemplate.colour = type.defaultColour; if(type.defaultWidth) lineTemplate.width = type.defaultWidth; if(type.defaultMode) lineTemplate.mode = type.defaultMode; return this._updateObjectStyles(lineTemplate, true); } }).then((res) => { return res.lineTemplate; }); }, getLine(padId, lineId) { return this._getPadObject("Line", padId, lineId); }, createLine(padId, data, trackPointsFromRoute) { return utils.promiseAuto({ defaultVals: this.getType(padId, data.typeId).then((type) => { if(type.defaultColour && !("colour" in data)) data.colour = type.defaultColour; if(type.defaultWidth && !("width" in data)) data.width = type.defaultWidth; if(type.defaultMode && !("mode" in data)) data.mode = type.defaultMode; }), routing: (defaultVals) => { return this._calculateRouting(data, trackPointsFromRoute); }, createLine: (routing, defaultVals) => { var dataCopy = utils.extend({ }, data); delete dataCopy.trackPoints; // They came if mode is track return this._createPadObject("Line", padId, dataCopy); }, lineEvent: (createLine) => { // We have to emit this before calling _setLinePoints so that this event is sent to the client first this.emit("line", padId, createLine); }, setLinePoints: (routing, createLine, lineEvent) => { return this._setLinePoints(padId, createLine.id, routing); }, updateStyle: (createLine) => { return this._updateObjectStyles(createLine, true); } }).then((res) => { this.emit("line", padId, res.setLinePoints); // res.linePoints returns the line with updated ascent and descent return res.setLinePoints; }); }, updateLine(padId, lineId, data, doNotUpdateStyles, trackPointsFromRoute) { return utils.promiseAuto({ originalLine: this.getLine(padId, lineId), routing: (originalLine) => { if(data.routePoints == null) data.routePoints = originalLine.routePoints; if(data.mode == null) data.mode = originalLine.mode || ""; if((data.mode == "track" && data.trackPoints) || !underscore.isEqual(data.routePoints, originalLine.routePoints) || data.mode != originalLine.mode) return this._calculateRouting(data, trackPointsFromRoute); // Also sets data.distance and data.time }, newLine: (routing) => { var dataCopy = utils.extend({ }, data); delete dataCopy.trackPoints; // They came if mode is track return this._updatePadObject("Line", padId, lineId, dataCopy, doNotUpdateStyles); }, updateStyle: (newLine) => { if(!doNotUpdateStyles) return this._updateObjectStyles(newLine, true); // Modifies newLine }, linePoints: (newLine, routing) => { if(routing) return this._setLinePoints(newLine.padId, lineId, routing); } }).then((res) => { this.emit("line", padId, res.linePoints || res.newLine); // res.linePoints returns the line with updated ascent and descent return res.linePoints; }); }, _setLinePoints(padId, lineId, trackPoints, _noEvent, _noUpdateLine) { // First get elevation, so that if that fails, we don't update anything let ascentDescent; return this._updateElevation(trackPoints).then((a) => { ascentDescent = a; return this._conn.model("LinePoint").destroy({ where: { lineId: lineId } }); }).then(() => { var create = [ ]; for(var i=0; i { if(!_noEvent) this.emit("linePoints", padId, lineId, points); if(!_noUpdateLine) // When deleting the line, we don't want to set this return this._updatePadObject("Line", padId, lineId, ascentDescent, true); }); }, deleteLine(padId, lineId) { return utils.promiseAuto({ line: this._deletePadObject("Line", padId, lineId), points: this._setLinePoints(padId, lineId, [ ], true, true) }).then((res) => { this.emit("deleteLine", padId, { id: lineId }); return res.line; }); }, getLinePointsForPad(padId, bboxWithZoom) { return utils.filterStreamPromise(this.getPadLines(padId, "id"), (line) => { return this.getLinePoints(line.id, bboxWithZoom).then((trackPoints) => { if(trackPoints.length >= 2) return { id: line.id, trackPoints: trackPoints }; }); }); }, getLinePoints(lineId, bboxWithZoom) { return Promise.resolve().then(() => { return this._conn.model("Line").build({ id: lineId }).getLinePoints({ where: Sequelize.and(this._makeBboxCondition(bboxWithZoom), bboxWithZoom ? { zoom: { lte: bboxWithZoom.zoom } } : null), attributes: [ "idx" ], order: "idx" }); }).then((data) => { // Get one more point outside of the bbox for each segment var indexes = [ ]; for(var i=0; i= 2) { line.distance = utils.calculateDistance(line.trackPoints); line.time = null; routing._calculateZoomLevels(line.trackPoints); for(var i=0; i= 2 && line.mode && line.mode != "track") { return routing.calculateRouting(line.routePoints, line.mode).then((routeData) => { line.distance = routeData.distance; line.time = routeData.time; for(var i=0; i (point.zoom < 12 && point.ele == null)); return elevation.getElevationForPoints(pointsToUpdate).then((elevations) => { elevations.forEach((elevation, i) => { if(pointsToUpdate[i].setDataValue) pointsToUpdate[i].setDataValue("ele", elevation); else pointsToUpdate[i].ele = elevation; }); return elevation.getAscentDescent(trackPoints.filter((point) => (point.ele != null)).map((point) => (point.ele))); }); } }); };