diff --git a/etc/default/templates/plan.html.njk b/etc/default/templates/plan.html.njk
new file mode 100644
index 0000000..80050e7
--- /dev/null
+++ b/etc/default/templates/plan.html.njk
@@ -0,0 +1,245 @@
+
+
+
+
+ {{projectId}} Lookahead plan
+
+
+
+
+
+
+
+
+Havila Charisma
+
+
+Planned sequences
+
+{% if lines.length %}
+
+
+
+ | Sequence |
+
+ Line |
+ FSP |
+ LSP |
+ Start |
+ End |
+ Length |
+ Azimuth |
+ Remarks |
+
+
+
+ {% for line in lines %}
+
+ | {{ line.sequence }} |
+
+ {{ line.line }} |
+ {{ line.fsp }} |
+ {{ line.lsp }} |
+ {{ line.ts0 |timestamp("minutes") }} |
+ {{ line.ts1 |timestamp("minutes") }} |
+ {{ (line.length/1000) |round(1) }} km |
+ {{ line.azimuth }}° |
+ {{ line.remarks |markdownInline }} |
+
+ {% endfor %}
+
+
+{% else %}
+(No plan)
+{% endif %}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/www/server/api/middleware/plan/list/html.js b/lib/www/server/api/middleware/plan/list/html.js
new file mode 100644
index 0000000..015f5f4
--- /dev/null
+++ b/lib/www/server/api/middleware/plan/list/html.js
@@ -0,0 +1,83 @@
+// const { configuration } = require('../../../../lib/db');
+const { plan, gis, info } = require('../../../../lib/db');
+const leafletMap = require('../../../../lib/map');
+const render = require('../../../../lib/render');
+
+// FIXME Refactor when able
+const defaultTemplatePath = require('path').resolve(__dirname, "../../../../../../../etc/default/templates/plan.html.njk");
+
+const html = async function (req, res, next) {
+ try {
+ const planInfo = await info.get(req.params.project, "plan", req.query);
+ const lines = await plan.list(req.params.project, req.query);
+ const preplotGeoJSON = await gis.project.preplot.lines(req.params.project, {class: "V", ...req.query});
+ const linesGeoJSON = lines.filter(plan => plan.geometry).map(plan => {
+ const feature = {
+ type: "Feature",
+ geometry: plan.geometry,
+ properties: plan
+ };
+ delete feature.properties.geometry;
+ return feature;
+ });
+
+// const template = (await configuration.get(req.params.project, "sse/templates/0/template")) || defaultTemplatePath;
+ const template = defaultTemplatePath;
+
+ const mapConfig = {
+ size: { width: 500, height: 500 },
+ layers: [
+ {
+ features: preplotGeoJSON,
+ options: {
+ style (feature) {
+ return {
+ opacity: feature.properties.ntba ? 0.2 : 0.5,
+ color: "gray",
+ weight: 1
+ }
+ }
+ }
+ },
+ {
+ features: linesGeoJSON,
+ options: {
+ style (feature) {
+ return {
+ color: "magenta",
+ weight: 2
+ }
+ }
+ }
+ }
+ ]
+ }
+
+ const map = leafletMap(mapConfig);
+
+ const data = {
+ projectId: req.params.project,
+ info: planInfo,
+ lines,
+ map: await map.getImageData()
+ }
+
+ const response = await render(data, template);
+
+ if ("download" in req.query || "d" in req.query) {
+ const extension = "html";
+ const filename = `${req.params.project.toUpperCase()}-Plan.${extension}`;
+ res.set("Content-Disposition", `attachment; filename="${filename}"`);
+ }
+ res.status(200).send(response);
+ next();
+ } catch (err) {
+ if (err.message.startsWith("template")) {
+ next({message: err.message});
+ } else {
+ next(err);
+ }
+ }
+};
+
+module.exports = html;
diff --git a/lib/www/server/api/middleware/plan/list/index.js b/lib/www/server/api/middleware/plan/list/index.js
index bdf0bbc..6f59b91 100644
--- a/lib/www/server/api/middleware/plan/list/index.js
+++ b/lib/www/server/api/middleware/plan/list/index.js
@@ -1,11 +1,15 @@
const json = require('./json');
const geojson = require('./geojson');
+const html = require('./html');
+const pdf = require('./pdf');
module.exports = async function (req, res, next) {
try {
const handlers = {
"application/json": json,
"application/geo+json": geojson,
+ "text/html": html,
+ "application/pdf": pdf
};
const mimetype = (handlers[req.query.mime] && req.query.mime) || req.accepts(Object.keys(handlers));
diff --git a/lib/www/server/api/middleware/plan/list/pdf.js b/lib/www/server/api/middleware/plan/list/pdf.js
new file mode 100644
index 0000000..b4e1735
--- /dev/null
+++ b/lib/www/server/api/middleware/plan/list/pdf.js
@@ -0,0 +1,97 @@
+const fs = require('fs/promises');
+const Path = require('path');
+const crypto = require('crypto');
+const { configuration } = require('../../../../lib/db');
+const { plan, gis, info } = require('../../../../lib/db');
+const leafletMap = require('../../../../lib/map');
+const render = require('../../../../lib/render');
+const { url2pdf } = require('../../../../lib/selenium');
+
+// FIXME Refactor when able
+const defaultTemplatePath = require('path').resolve(__dirname, "../../../../../../../etc/default/templates/plan.html.njk");
+
+function tmpname (tmpdir="/dev/shm") {
+ return Path.join(tmpdir, crypto.randomBytes(16).toString('hex')+".tmp");
+}
+
+const pdf = async function (req, res, next) {
+ const fname = tmpname();
+ try {
+ const planInfo = await info.get(req.params.project, "plan", req.query);
+ const lines = await plan.list(req.params.project, req.query);
+ const preplotGeoJSON = await gis.project.preplot.lines(req.params.project, {class: "V", ...req.query});
+ const linesGeoJSON = lines.filter(plan => plan.geometry).map(plan => {
+ const feature = {
+ type: "Feature",
+ geometry: plan.geometry,
+ properties: plan
+ };
+ delete feature.properties.geometry;
+ return feature;
+ });
+// const template = (await configuration.get(req.params.project, "sse/templates/0/template")) || defaultTemplatePath;
+ const template = defaultTemplatePath;
+
+
+ const mapConfig = {
+ size: { width: 500, height: 500 },
+ layers: [
+ {
+ features: preplotGeoJSON,
+ options: {
+ style (feature) {
+ return {
+ opacity: feature.properties.ntba ? 0.2 : 0.5,
+ color: "gray",
+ weight: 1
+ }
+ }
+ }
+ },
+ {
+ features: linesGeoJSON,
+ options: {
+ style (feature) {
+ return {
+ color: "magenta",
+ weight: 2
+ }
+ }
+ }
+ }
+ ]
+ }
+
+ const map = leafletMap(mapConfig);
+
+ const data = {
+ projectId: req.params.project,
+ info: planInfo,
+ lines,
+ map: await map.getImageData()
+ }
+
+ const html = await render(data, template);
+
+ await fs.writeFile(fname, html);
+ const pdf = Buffer.from(await url2pdf("file://"+fname), "base64");
+
+ if ("download" in req.query || "d" in req.query) {
+ const extension = "pdf";
+ const filename = `${req.params.project.toUpperCase()}-Plan.${extension}`;
+ res.set("Content-Disposition", `attachment; filename="${filename}"`);
+ }
+ res.status(200).send(pdf);
+ next();
+ } catch (err) {
+ if (err.message.startsWith("template")) {
+ next({message: err.message});
+ } else {
+ next(err);
+ }
+ } finally {
+ await fs.unlink(fname);
+ }
+};
+
+module.exports = pdf;