From 813abcd9d474faf03ba2d341dd5c1a56fb53575b Mon Sep 17 00:00:00 2001 From: djerom Date: Fri, 22 Apr 2022 18:34:31 +0500 Subject: [PATCH] init --- .gitignore | 9 ++ index.js | 8 ++ libs/bbox.js | 77 ++++++++++++++++++ libs/coord-system.js | 58 +++++++++++++ libs/file.js | 91 +++++++++++++++++++++ libs/math.js | 13 +++ libs/transform.js | 33 ++++++++ package.json | 16 ++++ services/map.js | 26 ++++++ services/sql/prod_inj.js | 116 ++++++++++++++++++++++++++ services/wells.js | 9 ++ svgmap/drawers/corel-layer.js | 3 + svgmap/drawers/svg-nodes.js | 134 ++++++++++++++++++++++++++++++ svgmap/drawers/svg-well.js | 30 +++++++ svgmap/drawers/well-head.js | 77 ++++++++++++++++++ svgmap/drawers/well-ring.js | 18 ++++ svgmap/helpers/style-adaptor.js | 48 +++++++++++ svgmap/helpers/style-defs.js | 50 ++++++++++++ svgmap/index.js | 0 svgmap/svg-map-builder.js | 62 ++++++++++++++ svgmap/svg-map-saver.js | 53 ++++++++++++ svgmap/svg-map-ui.js | 87 ++++++++++++++++++++ svgmap/svg-map.js | 140 ++++++++++++++++++++++++++++++++ svgmap/svg-node.js | 70 ++++++++++++++++ 24 files changed, 1228 insertions(+) create mode 100644 .gitignore create mode 100644 index.js create mode 100644 libs/bbox.js create mode 100644 libs/coord-system.js create mode 100644 libs/file.js create mode 100644 libs/math.js create mode 100644 libs/transform.js create mode 100644 package.json create mode 100644 services/map.js create mode 100644 services/sql/prod_inj.js create mode 100644 services/wells.js create mode 100644 svgmap/drawers/corel-layer.js create mode 100644 svgmap/drawers/svg-nodes.js create mode 100644 svgmap/drawers/svg-well.js create mode 100644 svgmap/drawers/well-head.js create mode 100644 svgmap/drawers/well-ring.js create mode 100644 svgmap/helpers/style-adaptor.js create mode 100644 svgmap/helpers/style-defs.js create mode 100644 svgmap/index.js create mode 100644 svgmap/svg-map-builder.js create mode 100644 svgmap/svg-map-saver.js create mode 100644 svgmap/svg-map-ui.js create mode 100644 svgmap/svg-map.js create mode 100644 svgmap/svg-node.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea42d72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/node_modules/ +/public/build/ +/tmp + +conf.js +package-lock.json +test + +.DS_Store diff --git a/index.js b/index.js new file mode 100644 index 0000000..b65bdb7 --- /dev/null +++ b/index.js @@ -0,0 +1,8 @@ +module.exports = { + MapBuilder: require('./svgmap/svg-map-builder'), + MapSaver: require('./svgmap/svg-map-saver'), + BBox: require('./libs/bbox'), + StyleAdaptor: require('./svgmap/helpers/style-adaptor'), + SvgNode: require('./svgmap/svg-node'), + SvgNodes: require('./svgmap/drawers/svg-nodes'), +} \ No newline at end of file diff --git a/libs/bbox.js b/libs/bbox.js new file mode 100644 index 0000000..a6b0cf4 --- /dev/null +++ b/libs/bbox.js @@ -0,0 +1,77 @@ +class BBox { + static fromLTWH(l, t, w, h){ + return BBox.fromLTRB(l, t, l + w, t + h) + } + + static fromLTRB(l, t, r, b){ + return Object.assign(new BBox(), {l: Math.min(l, r), t: Math.min(t, b), r: Math.max(l, r), b: Math.max(t, b)}) + } + + static from_array(arr_xy){ + const bbox = BBox.fromLTWH(arr_xy[0].x, arr_xy[0].y, 0, 0) + return bbox.append_many(arr_xy) + } + + w(){ + return this.r - this.l + } + + h(){ + return this.b - this.t + } + + toLTWH(){ + const {l, t, r, b} = this + return {l, t, w: r - l, h: b - t } + } + + toLTRB(){ + const {l, t, r, b} = this + return {l, t, r, b} + } + + toLTRBarr(){ + const {l, t, r, b} = this + return [l, t, r, b] + } + + append(x, y){ + this.l = Math.min(this.l, x) + this.t = Math.min(this.t, y) + this.r = Math.max(this.r, x) + this.b = Math.max(this.b, y) + return this + } + + append_many(arr_xy){ + arr_xy.forEach(e => this.append(e.x, e.y)) + return this + } + + scale(k){ + this.r = this.l + (this.r - this.l) * k + this.b = this.t + (this.b - this.t) * k + return this + } + + move(dx, dy){ + this.l += dx + this.t += dy + this.r += dx + this.b += dy + return this + } + + moveto(x, y){ + let dx = x - this.l + let dy = y - this.t + return this.move(dx, dy) + } + + clone(){ + return BBox.fromLTRB(this.l, this.t, this.r, this.b) + } +} + +module.exports = BBox +module.exports.default = BBox diff --git a/libs/coord-system.js b/libs/coord-system.js new file mode 100644 index 0000000..f61ab31 --- /dev/null +++ b/libs/coord-system.js @@ -0,0 +1,58 @@ +class CoordSystem{ + /** + * Создает ненормированную систему координат с началом в x0,y0 и направлением x1,y1 + * @param {*} x0 + * @param {*} y0 + * @param {*} x1 + * @param {*} y1 + * @returns + */ + constructor(x0, y0, x1, y1) { + this.x0 = x0 + this.y0 = y0 + this.x1 = x1 + this.y1 = y1 + } + + scale(k){ + this.x1 *= k + this.y1 *= k + return this + } + + move(dx, dy){ + this.x0 += dx + this.y0 += dy + this.x1 += dx + this.y1 += dy + return this + } + + moveto(x, y){ + let dx = x - this.x0 + let dy = y - this.y0 + return this.move(dx, dy) + } + + flipx(){ + let x = this.x0 + this.x0 = this.x1 + this.x1 = x + // this.x0 += 500 + // this.x1 = 2 * this.x0 - this.x1 + 500 + return this + } + + flipy(){ + this.y1 = 2 * this.y0 - this.y1 + return this + } + + clone(){ + return new CoordSystem(this.x0, this.y0, this.x1, this.y1) + } +}; + + +module.exports = CoordSystem +module.exports.default = CoordSystem diff --git a/libs/file.js b/libs/file.js new file mode 100644 index 0000000..edb392d --- /dev/null +++ b/libs/file.js @@ -0,0 +1,91 @@ +export default { + /** + * Downloads file + * @param {*} filename + * @param {*} data + */ + download(filename, data) { + var element = document.createElement("a"); + element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(data)); + element.setAttribute("download", filename); + + element.style.display = "none"; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + }, + + /** + * Convert data of file selected in input to base64 string + * @param {*} input + */ + async toBase64(input) { + return new Promise((resolve) => { + if (input.files.length == 0){ + resolve(null) + } + else if (input.files.length == 1){ + this.fileToBase64(input.files[0]).then(resolve) + } + else + Promise.all(Array(...input.files).map(this.fileToBase64)).then(resolve) + } + ); + }, + + /** + * Convert data of file selected in input to base64 string + * @param {*} input + */ + async fileToBase64(file) { + return new Promise((resolve, reject) => { + var reader = new FileReader(); + reader.readAsDataURL(file); + + reader.onload = function () { + resolve(reader.result) + }; + reader.onerror = function (error) { + reject(error) + }; + }); + }, + + /** + * Read data of file/files selected in input as string + * @param {*} input + */ + async read(input) { + return new Promise((resolve) => { + if (input.files.length == 0){ + resolve(null) + } + else if (input.files.length == 1){ + this.readFile(input.files[0]).then(resolve) + } + else + Promise.all(Array(...input.files).map(this.readFile)).then(resolve) + } + ); + }, + + /** + * Read data of file/files selected in input as string + * @param {*} input + */ + async readFile(file) { + return new Promise((resolve, reject) => { + var reader = new FileReader(); + reader.readAsText(file); + + reader.onload = function () { + resolve(reader.result) + }; + reader.onerror = function (error) { + reject(error) + }; + }); + }, +}; diff --git a/libs/math.js b/libs/math.js new file mode 100644 index 0000000..bd7e725 --- /dev/null +++ b/libs/math.js @@ -0,0 +1,13 @@ +export default { + /** + * Создает данные дл круговой диаграммы из набора значений v0,v1,v2,v3 -> [{a0: 0, a1: v0}, {a0: v0, a1: v0+v1}, {a0: v0+v1, a1: v0+v1+v2},] + * @param { [Number] } values Значения долей + * @param { boolean } norm Нормировать ли значения диапазонов в отрезок 0-1 + * @returns Массив с диапазонами долей 1,2,3,4 -> [{0,0.1},{0.1,0.3},{0.3,0.6},{0.6,1}] + */ + make_ranges(values, norm = true) { + let ranges = values.reduce((s, c, i) => [...s, {...c, a0: i && s[i - 1].a1, a1: c + (i && s[i - 1].a1)}], []) + let max = ranges[ranges.length - 1].a1 + return norm ? ranges.map(x => ({a0: x.a0 / max, a1: x.a1 / max})) : ranges + }, +}; diff --git a/libs/transform.js b/libs/transform.js new file mode 100644 index 0000000..131ce75 --- /dev/null +++ b/libs/transform.js @@ -0,0 +1,33 @@ +class Transfrom { + static fromCoordSyses(cs1, cs2){ + let tr = new Transfrom() + + tr.cs1 = cs1 + tr.cs2 = cs2 + + let dx1 = cs1.x1 - cs1.x0; + let dy1 = cs1.y1 - cs1.y0; + let dx2 = cs2.x1 - cs2.x0; + let dy2 = cs2.y1 - cs2.y0; + + tr.kx12 = dx1 ? dx2 / dx1 : 1; + tr.ky12 = dy1 ? dy2 / dy1 : 1; + + return tr + } + + trx(x){ + return (x - this.cs1.x0) * this.kx12 + this.cs2.x0; + } + + try(y){ + return (y - this.cs1.y0) * this.ky12 + this.cs2.y0; + } + + trp(p){ + return {x: this.trx(p.x), y: this.trx(p.y)} + } +} + +module.exports = Transfrom +module.exports.default = Transfrom \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6dd344b --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "app_mapbuilder", + "version": "1.0.0", + "description": "Svg map builder tools", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "http://git.dev.aurora.hub01.site/djerom/app_mapbuilder.git" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/services/map.js b/services/map.js new file mode 100644 index 0000000..b2142c7 --- /dev/null +++ b/services/map.js @@ -0,0 +1,26 @@ +module.exports = class { + + set_scale(scale){ + this.scale = scale + } + + get_bbox(data){ + return data.reduce( + (s, c) => ({ + l: Math.min(s.l, c.x), + t: Math.min(s.t, c.y), + r: Math.max(s.r, c.x), + b: Math.max(s.b, c.y), + }), + { l: data[0].x, t: data[0].y, r: data[0].x, b: data[0].y } + ) + } + + wells_layer(wells){ + + } + + render(){ + + } +} \ No newline at end of file diff --git a/services/sql/prod_inj.js b/services/sql/prod_inj.js new file mode 100644 index 0000000..f37dad9 --- /dev/null +++ b/services/sql/prod_inj.js @@ -0,0 +1,116 @@ +export default { + max_date(){ + return `SELECT MAX(date(year||'-01-01', (month - 1)||' month')) as date FROM production_injections` + }, + + devobjs(){ + return `SELECT distinct(object) as devobj FROM production_injections` + }, + + totals(devobj){ + return `SELECT + wells.well, wells.whx, wells.why + ,SUM(production_injections.woptm) as wopt + ,SUM(production_injections.wwptm) as wwpt + ,SUM(production_injections.wsgptv) as wgpt + ,SUM(production_injections.wwitv) as wwit + ,SUM(production_injections.days) as days + ,SUM(production_injections.wwptm)+SUM(production_injections.woptm) as wlpt + ,SUM(production_injections.wwptm)/(SUM(production_injections.wwptm)+SUM(production_injections.woptm)) as wlf + FROM + wells, production_injections + WHERE + wells.well=production_injections.well + AND production_injections.object='${devobj}' + GROUP BY + wells.well + ORDER BY + wlpt DESC, wwit DESC` + }, + + rates(devobj, date){ + date = new Date(date) + const year_month = date.getFullYear()*100 + date.getMonth() + return `SELECT + wells.well, wells.whx, wells.why + ,SUM(production_injections.woptm)/SUM(production_injections.days) as wopr + ,SUM(production_injections.wwptm)/SUM(production_injections.days) as wwpr + ,SUM(production_injections.wsgptv)/SUM(production_injections.days) as wgpr + ,SUM(production_injections.wwitv)/SUM(production_injections.days) as wwir + ,SUM(production_injections.days) as days + ,(SUM(production_injections.wwptm)+SUM(production_injections.woptm)) / SUM(production_injections.days) as wlpr + ,SUM(production_injections.wwptm)/(SUM(production_injections.wwptm)+SUM(production_injections.woptm)) as wlf + FROM + wells, production_injections + WHERE + wells.well=production_injections.well + AND production_injections.object='${devobj}' + AND (year*100+month)=${year_month} + GROUP BY + wells.well` + }, + + + production_injection(field, devobj, date){ + const f_date = date ? `AND year*1000+month=${date.getFullYear()*1000+date.getMonth()}` : '' + console.log(f_date) + + return `SELECT + wells.well, wells.whx, wells.why + ,SUM(production_injections.woptm) as wopt + ,SUM(production_injections.wwptm) as wwpt + ,SUM(production_injections.wsgptv) as wgpt + ,SUM(production_injections.wwitv) as wwit + ,SUM(production_injections.days) as days + ,SUM(production_injections.wwptm)+SUM(production_injections.woptm) as wlpt + ,SUM(production_injections.wwptm)/(SUM(production_injections.wwptm)+SUM(production_injections.woptm)) as wlf + FROM + wells, production_injections + WHERE + AND wells.well=production_injections.well + AND production_injections.object='${devobj}' + AND (year*1000+month)=${max_year_month} + GROUP BY + wells.well` + }, + + production_total(field, devobj){ + return `SELECT + wells.well, wells.whx, wells.why + ,SUM(production_injections.woptm) as wopt + ,SUM(production_injections.wwptm) as wwpt + ,SUM(production_injections.wsgptv) as wgpt + ,SUM(production_injections.wwitv) as wwit + ,SUM(production_injections.wwptm)+SUM(production_injections.woptm) as wlpt + ,SUM(production_injections.wwptm)/(SUM(production_injections.wwptm)+SUM(production_injections.woptm)) as wlf + FROM + wells, production_injections + WHERE + wells.well=production_injections.well + AND object='${devobj}' + GROUP BY + wells.well` + }, + + injection_total(){ + const query_all = `SELECT + wells.well, wells.whx, wells.why + ,SUM(production_injections.woptm) as wopt + ,SUM(production_injections.wwptm) as wwpt + ,SUM(production_injections.wsgptv) as wgpt + ,SUM(production_injections.wwitv) as wwit + ,SUM(production_injections.wwptm)+SUM(production_injections.woptm) as wlpt + ,SUM(production_injections.wwptm)/(SUM(production_injections.wwptm)+SUM(production_injections.woptm)) as wlf + FROM + wells, production_injections + WHERE + wells.well=production_injections.well + AND object='${devobj}' + GROUP BY + wells.well + HAVING + wwit>0 + + ` + } +} \ No newline at end of file diff --git a/services/wells.js b/services/wells.js new file mode 100644 index 0000000..f06370e --- /dev/null +++ b/services/wells.js @@ -0,0 +1,9 @@ +module.exports = class { + constructor(url){ + this.url = url + } + + get_wells(){ + + } +} \ No newline at end of file diff --git a/svgmap/drawers/corel-layer.js b/svgmap/drawers/corel-layer.js new file mode 100644 index 0000000..1965f55 --- /dev/null +++ b/svgmap/drawers/corel-layer.js @@ -0,0 +1,3 @@ +const SvgNodes = require("./svg-nodes"); + +module.exports = (name) => SvgNodes.group(['']).set_attr("id", name); \ No newline at end of file diff --git a/svgmap/drawers/svg-nodes.js b/svgmap/drawers/svg-nodes.js new file mode 100644 index 0000000..8f5788a --- /dev/null +++ b/svgmap/drawers/svg-nodes.js @@ -0,0 +1,134 @@ +const SvgNode = require("./../svg-node.js"); + +function svg() { + return new SvgNode("svg", { + version: "1.1", + xmlns: "http://www.w3.org/2000/svg", + "xml:space": "preserve", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + style: "shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd", + // viewBox: "0 0 2000 2000" + }); +} + +function node(name, attrs, items) { + return new SvgNode(name, attrs, items); +} + +function container(items) { + return new SvgNode(null, null, items); +} + +function group(items) { + return new SvgNode("g", null, items); +} + +function circle(r) { + return new SvgNode("circle", { r }); +} + +function ngon(r, n, ang = 0) { + const da = (2 * Math.PI) / n; + ang = (ang * Math.PI) / 180; + const pts = Array(n) + .fill(0) + .map((x, i) => [Math.sin(i * da + ang) * r, Math.cos(i * da + ang) * r]); + return new SvgNode("polygon", { points: pts.map((x) => `${x[0]},${x[1]}`) }); +} + +function spike1(w, h, ang) { + const da = (ang * Math.PI) / 180; + const sa = Math.sin(da); + const ca = Math.cos(da); + h = h / 2; + const pts = `${-sa * h},${-ca * h} ${ca * w},${-sa * w}, ${sa * h},${ca * h}`; + return new SvgNode("polygon", { points: pts }); +} + +function sector(r, a0, a1) { + const k = Math.PI / 180; + let s0 = -Math.sin(a0 * k) * r; + let c0 = Math.cos(a0 * k) * r; + let s1 = -Math.sin(a1 * k) * r; + let c1 = Math.cos(a1 * k) * r; + + var aa = a1 - a0 > 180 ? 1 : 0; + // var p = new TimalSvg.Data.SvgPath(string.Format("M0 0 L{0} 0 A {0} {0} 0 {3} 0 {1} {2} z", FN(r), ca, sa, aa)); + // p.Transform = string.Format("rotate({0})", FN(ang / 2)); + + return new SvgNode("path", { d: `M0 0 L${c0} ${s0} A ${r} ${r} 0 ${aa} 0 ${c1} ${s1} z` }); +} + +function ring_sector(r0, r1, a0, a1) { + const k = Math.PI / 180; + let s0 = -Math.sin(a0 * k); + let c0 = Math.cos(a0 * k); + let s1 = -Math.sin(a1 * k); + let c1 = Math.cos(a1 * k); + + var aa = a1 - a0 > 180 ? 1 : 0; + + return new SvgNode("path", { + d: `M${c0 * r0} ${s0 * r0} + A ${r0} ${r0} 0 ${aa} 0 ${c1 * r0} ${s1 * r0} + L${c1 * r1} ${s1 * r1} + A ${r1} ${r1} 0 ${aa} 1 ${c0 * r1} ${s0 * r1} z`, + }); +} + +/** + * + * @param r0 + * @param r1 + * @param a0 + * @param a1 + * @param { [{v, style}] } rings + */ +function ring_sectors(r0, r1, sectors) { + let sum = sectors.reduce((s, c) => s + c.v, 0); + let angs = sectors.reduce((s, c, i) => [...s, { ...c, a0: i && s[i - 1].a1, a1: c.v + (i && s[i - 1].a1) }], []); + let items = angs.map((x) => ring_sector(r0, r1, (x.a0 * 360) / sum, (x.a1 * 360) / sum).add_style(x.style)); + + return group(items); +} + +function text(text) { + let node = new SvgNode("text"); + node.items = [text]; + node.set_attrs({ x: 0, y: 0 }); + return node; +} + +const defs = { + simple: { + radialGradient(id, color0, color1) { + let s0 = new SvgNode("stop", { offset: "0%", "stop-color": color0 }); + let s1 = new SvgNode("stop", { offset: "100%", "stop-color": color1 }); + return new SvgNode("radialGradient", { id }).append(s0, s1); + }, + }, + + radialGradient(id, cx, cy, r) { + return new SvgNode("radialGradient", { id, cx, cy, r }); + }, + + stop(offset, color) { + return new SvgNode("stop", { offset, "stop-color": color }); + }, +}; + +module.exports = { + node, + svg, + container, + group, + circle, + ngon, + spike1, + sector, + ring_sector, + ring_sectors, + text, +}; + +module.exports.default = module.exports; diff --git a/svgmap/drawers/svg-well.js b/svgmap/drawers/svg-well.js new file mode 100644 index 0000000..8af8608 --- /dev/null +++ b/svgmap/drawers/svg-well.js @@ -0,0 +1,30 @@ +// export default { +// wellhead: { +// prod(x, y, r, style){ +// return SvgNodes.circle(r).add_style(style).move(w.x, w.y); +// }, + +// inj(x, y, r, style){ +// return SvgNodes.circle(r).add_style(style).move(w.x, w.y); +// } +// }, + +// // [{x,y}] +// heads(wells, r, style) { +// return wells.map((w) => SvgNodes.circle(r).add_style(style).move(w.x, w.y)); +// }, + +// // [{x,y,name}] +// names(wells, style, shift) { +// return wells.map((w) => +// SvgNodes.text(w.name) +// .add_style(style) +// .move(w.x + shift.x, w.y + shift.y) +// ); +// }, + +// // {x,y,ring[1,2,3,4,5]} +// ring(x, y, r0, r1, a0, a1, style) { +// return SvgNodes.ring_sector(r0, r1, a0, a1).move(x, y).add_style(style); +// }, +// } \ No newline at end of file diff --git a/svgmap/drawers/well-head.js b/svgmap/drawers/well-head.js new file mode 100644 index 0000000..1ba1d2d --- /dev/null +++ b/svgmap/drawers/well-head.js @@ -0,0 +1,77 @@ +const SvgNode = require("./../svg-node.js"); +const SvgNodes = require("./svg-nodes.js"); + +function name(text, ppu, styles) { + return SvgNodes.text(text) + .move(styles["wellhead-name"].dx * ppu, styles["wellhead-name"].dy * ppu) + .add_style(styles["wellhead-name"]); +} + +function wlp(wlf, ppu, styles) { + return SvgNodes.text(`${Math.round(wlf * 1000) / 10 || ""}%`) + .move(styles.wlf.dx * ppu, styles.wlf.dy * ppu) + .add_style(styles.wlf); +} + +/** + * Добывающая скважина + * @param {*} ppu + * @param {*} styles + * @returns + */ +function prod(ppu, styles) { + return SvgNodes.circle(1.5 * ppu).add_style(styles["wellhead-black"]); +} + +/** + * Нагнетательная + * @param {*} ppu + * @param {*} styles + * @returns + */ +function inj(ppu, styles) { + const style_spike = { + "stroke-width": "0", + fill: "#000", + }; + + const style_blue = { + "stroke-width": Math.round(styles._units["1pt"]), + stroke: "#000", + fill: "#00f", + }; + + return SvgNodes.group([ + SvgNodes.group([ + SvgNodes.spike1(1.8 * ppu, 1.7 * ppu, 0).move(2 * ppu, 0), + SvgNodes.spike1(1.8 * ppu, 1.7 * ppu, 90).move(0, -2 * ppu), + SvgNodes.spike1(1.8 * ppu, 1.7 * ppu, 180).move(-2 * ppu, 0), + SvgNodes.spike1(1.8 * ppu, 1.7 * ppu, 270).move(0, 2 * ppu), + ]).add_style(style_spike), + new SvgNode("line", { x1: -2 * ppu, x2: 2 * ppu, y1: 0, y2: 0 }), + new SvgNode("line", { y1: -2 * ppu, y2: 2 * ppu, x1: 0, x2: 0 }), + SvgNodes.circle(1.5 * ppu).add_style(style_blue), + ]).add_style(style_blue); +} + +/** + * Серая с треугольником с малым дебитом/нагнетанием + * @param {*} ppu + * @param {*} styles + * @returns + */ +function gray(ppu, styles) { + return SvgNodes.group([ + SvgNodes.circle(1.5 * ppu).add_style(styles["wellhead-gray"]), + SvgNodes.ngon(1.5 * ppu, 3, 0).add_style(styles["wellhead-black"]), + ]); +} + +module.exports = { + name, + wlp, + prod, + inj, + gray +}; +module.exports.default = module.exports; diff --git a/svgmap/drawers/well-ring.js b/svgmap/drawers/well-ring.js new file mode 100644 index 0000000..6572b9d --- /dev/null +++ b/svgmap/drawers/well-ring.js @@ -0,0 +1,18 @@ +const SvgNodes = require("./svg-nodes"); + +function pt(wopt, wwpt, rmm, ppu, style) { + return SvgNodes.ring_sectors(1.5 * ppu, rmm * ppu, [ + { v: wopt, style: style.opt }, + { v: wwpt, style: style.wpt }, + ]); +} + +function it(rmm, ppu, style) { + return SvgNodes.circle(rmm * ppu).add_style(style.wit); +} + +module.exports = { + pt, + it, +}; +module.exports.default = module.exports; diff --git a/svgmap/helpers/style-adaptor.js b/svgmap/helpers/style-adaptor.js new file mode 100644 index 0000000..405265e --- /dev/null +++ b/svgmap/helpers/style-adaptor.js @@ -0,0 +1,48 @@ +/** + * Преобразовать размер шрифта в единицы карты + * @param { string | Number } v Значение для перевода "10mm", "22pt" + * @param {*} ppu Pixel per unit + * @returns + */ +function size2ppu(v, ppu) { + if (typeof v !== "string") return v; + + if (v.endsWith("mm")) { + return parseFloat(v.substring(0, v.length - 2) * ppu * 100) / 69; + } + if (v.endsWith("pt")) { + return (parseFloat(v.slice(0, v.length - 2)) * ppu * 100) / 283.46; + } +} + +/** + * Перегоняет стиль в единицы карты (возвращает новый объект) + * @param {*} style Объект стиля для преобразования + * @param {*} ppu Pixel per unit + */ +function update_style(style, ppu) { + let s = { ...style }; + const convertable = ["font-size", "stroke-width", "1pt", "1mm"]; + Object.keys(s).forEach((k) => { + if (convertable.includes(k)) s[k] = size2ppu(s[k], ppu); + }); + return s; +} + +/** + * Перегоняет все стили в единицы карты (возвращает новый объект) + * @param {*} style Объект стиля для преобразования + * @param {*} ppu Pixel per unit + */ +function update_styles(styles, ppu) { + return Object.keys(styles).reduce((s, c) => ({ ...s, [c]: update_style(styles[c], ppu) }), {}); +} + + +module.exports = { + size2ppu, + update_style, + update_styles +}; + +module.exports.default = module.exports; diff --git a/svgmap/helpers/style-defs.js b/svgmap/helpers/style-defs.js new file mode 100644 index 0000000..6a49051 --- /dev/null +++ b/svgmap/helpers/style-defs.js @@ -0,0 +1,50 @@ +const SvgNode = require("../svg-node"); + +function simple_radial_gradient(id, color0, color1) { + let s0 = new SvgNode("stop", { offset: "0%", "stop-color": color0 }); + let s1 = new SvgNode("stop", { offset: "100%", "stop-color": color1 }); + return new SvgNode("radialGradient", { id }).append(s0, s1); +} + +function radial_gradient(id, cx, cy, r) { + return new SvgNode("radialGradient", { id, cx, cy, r }); +} + +function stop(offset, color) { + return new SvgNode("stop", { offset, "stop-color": color }); +} + +/** + * Extracts from styles items which must be in defs section. + * @param {*} style + */ +function extract_style_defs(style) { + const skeys = Object.keys(style).filter((x) => x.startsWith("$")); + + return skeys.map((skey) => extract_class_def(style[skey], skey.substring(1))) +} + +/** + * Extracts from styles items which must be in defs section. + * @param {*} style + */ +function extract_class_def(cls, id) { + if (cls.type == "radial-gradient") { + const ckeys = Object.keys(cls); + const stops = ckeys + .filter((x) => x.endsWith("%")) + .map((x) => new SvgNode("stop", { offset: x, "stop-color": cls[x] })); + + return new SvgNode("radialGradient", { id }, stops); + } + + throw `Unknown system style ${cls.type}` +} + +module.exports = { + simple_radial_gradient, + radial_gradient, + extract_style_defs, +}; + +module.exports.default = module.exports; diff --git a/svgmap/index.js b/svgmap/index.js new file mode 100644 index 0000000..e69de29 diff --git a/svgmap/svg-map-builder.js b/svgmap/svg-map-builder.js new file mode 100644 index 0000000..7d68a95 --- /dev/null +++ b/svgmap/svg-map-builder.js @@ -0,0 +1,62 @@ +const SvgNode = require("./svg-node.js"); +const SvgNodes = require("./drawers/svg-nodes.js"); +const Defs = require('./helpers/style-defs') +const well_ring = require('./drawers/well-ring') +const well_head = require('./drawers/well-head') +const corel_layer = require('./drawers/corel-layer') + +function build_pt_layer(wells, settings, style) { + function t2r(tons) { + // tonns/mm2 = tonns/cm2 / 100. S(mm)=Pi*r*r=tons/tons_in_cm2*100. r = sqrt(tons/tons_in_cm2*100 / PI) + return Math.sqrt(((tons / settings.tons_in_cm2) * 100) / Math.PI); + } + + let { ppu } = settings; + + let svg = corel_layer("WWPT"); + // Круги + svg.append(wells.map((x) => well_ring.pt(x.wopt, x.wwpt, t2r(x.wlpt), ppu, style).move(x.lx, x.ly))); + + // Знак скважины + svg.append( + wells.map((x) => well_head[t2r(x.wlpt) > 1.6 ? "prod" : "gray"](ppu, style).move(x.lx, x.ly)) + ); + + // Имя скважины + svg.append(wells.map((x) => well_head.name(x.name, ppu, style).move(x.lx, x.ly))); + // Обводненность + svg.append(wells.map((x) => well_head.wlp(x.wlf, ppu, style).move(x.lx, x.ly))); + + return { svg }; +} + +function build_it_layer(wells, settings) { + function t2r(tons) { + // tonns/mm2 = tonns/cm2 / 100. S(mm)=Pi*r*r=tons/tons_in_cm2*100. r = sqrt(tons/tons_in_cm2*100 / PI) + return Math.sqrt(((tons / settings.tons_in_cm2) * 100) / Math.PI); + } + + let { ppu, style } = settings; + + let svg = corel_layer("WWIT"); + + // Круги + svg.append(wells.map((x) => SvgWellNodes.ring.it(t2r(x.wwit), ppu, style).move(x.lx, x.ly))); + + // Знак скважины + svg.append( + wells.map((x) => SvgWellNodes.wellhead[t2r(x.wwit) > 1.6 ? "inj" : "gray"](ppu, style).move(x.lx, x.ly)) + ); + + // Имя скважины + svg.append(wells.map((x) => SvgWellNodes.wellhead.name(x.name, ppu, style).move(x.lx, x.ly))); + + return { svg }; +} + +module.exports = { + build_pt_layer, + build_it_layer, +}; + +module.exports.default = module.exports; diff --git a/svgmap/svg-map-saver.js b/svgmap/svg-map-saver.js new file mode 100644 index 0000000..45b28f4 --- /dev/null +++ b/svgmap/svg-map-saver.js @@ -0,0 +1,53 @@ +const CoordSystem = require("../libs/coord-system"); +const BBox = require("../libs/bbox"); +const transfrom = require("../libs/transform"); +// const SvgMapBuilder = require("./svg-map-builder"); +const SvgNodes = require("./drawers/svg-nodes"); +const adaptor = require("./helpers/style-adaptor"); +const { extract_style_defs } = require('./helpers/style-defs'); + +module.exports = class SvgSaver { + constructor(settings, style, bbox) { + // Функция перевода координат из глобальных в локальные. + const cs1 = new CoordSystem(bbox.l, bbox.t, bbox.r, bbox.b); + const cs_mm = cs1.clone().flipy().moveto(0, 0).scale(settings.map_scale * 1000); + const cs_ppu = cs_mm.clone().scale(settings.ppu); + const csw = Math.abs(cs_mm.x1 - cs_mm.x0); + const csh = Math.abs(cs_mm.y1 - cs_mm.y0); + const bbox_ppu = BBox.fromLTRB(cs_ppu.x0, cs_ppu.y0, cs_ppu.x1, cs_ppu.y1); + + this.tr = transfrom.fromCoordSyses(cs1, cs_ppu); + + this.settings = settings + this.style = adaptor.update_styles(style, settings.ppu); + const defs = extract_style_defs(style) + + this.svg = SvgNodes.svg() + .set_attr("width", csw + "mm") + .set_attr("height", csh + "mm") + .set_attr("viewBox", `${bbox_ppu.l} ${bbox_ppu.t} ${bbox_ppu.w()} ${bbox_ppu.h()}`); + + if (defs.length){ + this.svg.append(SvgNodes.node('defs', null, defs)) + } + + // console.log(style) + } + + append_defs(defs) { + this.svg.append(defs); + } + + append_svg(svg) { + this.svg.append(svg); + } + + append_layer(layer) { + this.append_defs(layer.defs); + this.append_svg(layer.svg); + } + + render() { + return this.svg.render(); + } +}; diff --git a/svgmap/svg-map-ui.js b/svgmap/svg-map-ui.js new file mode 100644 index 0000000..c7bdb9a --- /dev/null +++ b/svgmap/svg-map-ui.js @@ -0,0 +1,87 @@ +export default class SvgMapUI { + constructor(host) { + this.host = host + this.scale = 1 + this.pos = { x: 0, y: 0 } + console.log(host) + this.host.onwheel = this.handle_wheel.bind(this) + this.host.onmousedown = this.handle_mousedown.bind(this) + this.host.onmousemove = this.handle_mousemove.bind(this) + this.host.onmouseup = this.handle_mouseup.bind(this) + + this.is_down = false + + this.set_scale(1) + } + + + set(html){ + this.host.innerHTML = html + } + + handle_mousedown(event) { + this.is_down = true + this.downin = { x: event.clientX, y: event.clientY } + console.log(event) + // clientX: 451, clientY: 297 + // layerX: 451, layerY: 297 + } + + handle_mousemove(event) { + if (this.is_down) { + event.preventDefault() + let dx = event.clientX - this.downin.x + let dy = event.clientY - this.downin.y + + this.pos.x -= dx * this.scale + this.pos.y -= dy * this.scale + this.set_scale(this.scale) + this.downin = { x: event.clientX, y: event.clientY } + // this.downin = {x: event.clientX, y: event.clientY} + } + // console.log(event.x, event.clientX + this.pos.x, this.scale) + } + + handle_mouseup(event) { + this.is_down = false + } + + + handle_wheel(event) { + event.preventDefault() + + if (event.ctrlKey) { + if (event.deltaY > 0) { + this.scale_up() + } + else + this.scale_down() + } + + else if (event.shiftKey) { + console.log('shifted') + } + + else { + + } + + console.log(event) + } + + set_scale(scale) { + this.scale = scale + if (!this.host.childNodes.length) return; + this.host.childNodes[0].setAttribute("viewBox", `${this.pos.x} ${this.pos.y} ${2000 * this.scale} ${2000 * this.scale}`) + // this.host.childNodes[0].setAttribute("width", `1000`) + // this.host.childNodes[0].setAttribute("height", `1000`) + } + + scale_up() { + this.set_scale(this.scale * 1.1) + } + + scale_down() { + this.set_scale(this.scale * 0.9) + } +} \ No newline at end of file diff --git a/svgmap/svg-map.js b/svgmap/svg-map.js new file mode 100644 index 0000000..b775da8 --- /dev/null +++ b/svgmap/svg-map.js @@ -0,0 +1,140 @@ +import { parse } from "svg-parser"; + +import SvgNode from './svg-node.js' +import SvgNodes from './svg-nodes.js' + +export default class { + async parse(xml) { + let result1 = parse(xml); + + this.svg = result1.children.filter((x) => x.tagName == "svg")[0]; + } + + /** + * Получить слои документа. Имя слоя можно получить через поле id. + * @returns + */ + get_layers() { + return this.svg.children.filter((x) => x.tagName == "g"); + } + + /** + * Получить данные масштаба карты. + * @returns {w, h, k, u} Ширина в мм, высота в мм, k * v[px] = v[мм], юниты (mm, cm, inch) + */ + get_map_scale() { + // console.log(this.svg) + let vb = this.svg.properties.viewBox.split(" ").map((x) => parseFloat(x)); + let w = this.svg.properties.width; + let h = this.svg.properties.height; + + let units = { + in: 25.4, + mm: 1, + cm: 10, + px: 0.0846646, + // '%': 1 + }; + + const u = Object.keys(units).filter((x) => w.endsWith(x))[0]; + if (!u) throw "Unsupported units"; + + w = Math.round(parseFloat(w.substring(0, w.length - u.length)) * units[u] * 100) / 100; + h = Math.round(parseFloat(h.substring(0, h.length - u.length)) * units[u] * 100) / 100; + let k = vb[2] / w; + + return { w, h, k, u }; + } + + /** + * Достать из слоя опордные скважины + * @param {*} layer Слой в котором 2 последние группы содержат опорные скважины. + * @returns { w1, w2 } Опрорные скважины + */ + extract_anchor(layer) { + let w1 = this.extract_well(layer.children[layer.children.length - 1]); + let w2 = this.extract_well(layer.children[layer.children.length - 2]); + + return { w1, w2 }; + } + + _flat(node) { + if (!node.children) return [node]; + return node.children.reduce((s, c) => s.concat(this._flat(c)), [node]); + } + + /** + * Получить из группы данные скважины (имя, координаты) + * @param {*} pivot_group Svg группа + * @returns { {x, y, name} } + */ + extract_well(pivot_group) { + let flat = this._flat(pivot_group); + let ellipse = flat.filter((x) => x.tagName == "circle" || x.tagName == "ellipse")[0]; + let tr = ellipse.properties.transform + .replace("matrix(", "") + .replace(")", "") + .split(" ") + .map((x) => parseFloat(x)); + + let text = flat.filter((x) => x.type == "text")[0]; + let name = text.value; + return { x: tr[4], y: tr[5], name }; + } + + build_tp_layer(wells, settings){ + const sc = this.get_map_scale() + + function t2r(tons){ + // tonns/mm2 = tonns/cm2 / 100. S(mm)=Pi*r*r=tons/tons_in_cm2*100. r = sqrt(tons/tons_in_cm2*100 / PI) + return Math.sqrt(tons / settings.tons_in_cm2 * 100 / Math.PI) + } + + let {styles} = settings + + let defs = new SvgNode("defs") + defs.append(SvgNodes.defs.simple.radialGradient("rg_opt", "#fff", "#B86B41")) + defs.append(SvgNodes.defs.simple.radialGradient("rg_wpt", "#fff", "#67c2e5")) + + let svg = SvgNodes.group(['']).set_attr("id", "WWPT") + // Круги + svg.append(wells.map((x) => SvgNodes.ring_sectors(1.5 * sc.k, t2r(x.wlpt) * sc.k, [{v: x.wopt, style: styles.opt}, {v: x.wwpt, style: styles.wpt}]).move(x.lx, x.ly))); + // Знак скважины + svg.append(wells.map((x) => SvgNodes.circle(1.5 * sc.k).move(x.lx, x.ly).add_style(styles.wellhead))); + // Имя скважины + svg.append(wells.map((x) => SvgNodes.text(x.well).move(x.lx + styles.wellhead.dx * sc.k, x.ly + styles.wellhead.dy * sc.k).add_style(styles.wellhead))); + // Обводненность + svg.append(wells.map((x) => SvgNodes.text(`${Math.round(x.wlf * 1000)/10 || ''}%`).move(x.lx + styles.wlf.dx * sc.k, x.ly + styles.wlf.dy * sc.k).add_style(styles.wlf))); + + return {defs, svg} + } + + build_ti_layer(wells, settings){ + const sc = this.get_map_scale() + + function t2r(tons){ + // tonns/mm2 = tonns/cm2 / 100. S(mm)=Pi*r*r=tons/tons_in_cm2*100. r = sqrt(tons/tons_in_cm2*100 / PI) + return Math.sqrt(tons / settings.tons_in_cm2 * 100 / Math.PI) + } + + let {styles} = settings + + let defs = SvgNodes.container() + defs.append(SvgNodes.defs.simple.radialGradient("rg1", "#fff", "#c1ff5e")) + + let svg = SvgNodes.group(['']).set_attr("id", "WWIT") + // Круги + // let g_rings = SvgNodes.group(['']).set_attr("id", "Круги") + // svg.append(g_rings) + svg.append(wells.map((x) => SvgNodes.circle(t2r(x.wwit) * sc.k).add_style(styles.wit).move(x.lx, x.ly))); + // Знак скважины + svg.append(wells.map((x) => SvgNodes.circle(1.5 * sc.k).move(x.lx, x.ly).add_style(styles.wellhead))); + // Имя скважины + svg.append(wells.map((x) => SvgNodes.text(x.well).move(x.lx + styles.wellhead.dx * sc.k, x.ly + styles.wellhead.dy * sc.k).add_style(styles.wellhead))); + // Обводненность + svg.append(wells.map((x) => SvgNodes.text(`${Math.round(x.wlf * 1000)/10 || ''}%`).move(x.lx + styles.wlf.dx * sc.k, x.ly + styles.wlf.dy * sc.k).add_style(styles.wlf))); + + return {defs, svg} + } + +} diff --git a/svgmap/svg-node.js b/svgmap/svg-node.js new file mode 100644 index 0000000..ca19a47 --- /dev/null +++ b/svgmap/svg-node.js @@ -0,0 +1,70 @@ +class SvgNode { + constructor(tag, attrs, items) { + this.tag = tag + this.attrs = attrs || {} + this.items = items || [] + } + + append(...items){ + this.items.push(...items) + return this + } + + set_attr(attr, value) { + this.attrs[attr] = value + return this + } + + set_attrs(attrs) { + this.attrs = { ...this.attrs, ...attrs } + return this + } + + add_style(style) { + this.style = { ...this.style || {}, ...style } + return this + } + + clear_style() { + this.style = null + return this + } + + move(x, y) { + if (this.transform) + this.transform = { x: this.transform.x + x, y: this.transform.y + y } + else + this.transform = { x, y } + return this + } + + get(attr) { + + } + + _render_node(node) { + // console.log(typeof node) + // if (typeof node == 'undefined') return '' + if (typeof node == 'string') return node + else if (typeof node == 'object' && node.tag) return node.render() + else if (Array.isArray(node)) return node.map(this._render_node).join('') + else return '' + } + + render() { + let attrs = Object.keys(this.attrs).filter(x => this.attrs[x]).map(x => ` ${x}="${this.attrs[x]}"`).join('') + // console.log(this.style ? Object.keys(this.style).map(x => `${x}:${this.style[x]}`) : '') + let style = this.style ? ' style="' + Object.keys(this.style).map(x => `${x}:${this.style[x]}`).join('; ') + '"' : '' + let transfrom = this.transform ? ` transform="translate(${this.transform.x},${this.transform.y})"` : '' + let items = this.items ? this.items.map(x => this._render_node(x)).join('') : '' + + // console.log('this.items', this.items, items) + if (this.tag) + return `<${this.tag}${attrs}${style}${transfrom}>${items}` + else + return items + } +} + +module.exports = SvgNode +module.exports.default = module.exports; \ No newline at end of file