zero2geospatial3d-sdk 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. zero2geospatial3d/__init__.py +105 -0
  2. zero2geospatial3d/acoustics.py +284 -0
  3. zero2geospatial3d/classify.py +379 -0
  4. zero2geospatial3d/cli.py +367 -0
  5. zero2geospatial3d/comfort.py +687 -0
  6. zero2geospatial3d/data/epw_stations.json.gz +0 -0
  7. zero2geospatial3d/daylight.py +337 -0
  8. zero2geospatial3d/dispersion.py +249 -0
  9. zero2geospatial3d/epw.py +162 -0
  10. zero2geospatial3d/geo.py +138 -0
  11. zero2geospatial3d/grid.py +119 -0
  12. zero2geospatial3d/microclimate.py +388 -0
  13. zero2geospatial3d/morphology.py +283 -0
  14. zero2geospatial3d/nsga2.py +315 -0
  15. zero2geospatial3d/osm.py +450 -0
  16. zero2geospatial3d/osm_tags.py +180 -0
  17. zero2geospatial3d/py.typed +0 -0
  18. zero2geospatial3d/results.py +231 -0
  19. zero2geospatial3d/road_sources.py +141 -0
  20. zero2geospatial3d/scene.py +555 -0
  21. zero2geospatial3d/server.py +332 -0
  22. zero2geospatial3d/shading.py +443 -0
  23. zero2geospatial3d/solar.py +207 -0
  24. zero2geospatial3d/terrain.py +610 -0
  25. zero2geospatial3d/urban_form.py +326 -0
  26. zero2geospatial3d/walkability.py +380 -0
  27. zero2geospatial3d/weather.py +373 -0
  28. zero2geospatial3d/web/index.html +50 -0
  29. zero2geospatial3d/web/src/analysis/climate.js +278 -0
  30. zero2geospatial3d/web/src/analysis/compliance.js +345 -0
  31. zero2geospatial3d/web/src/analysis/gpu.js +431 -0
  32. zero2geospatial3d/web/src/analysis/optimize.js +208 -0
  33. zero2geospatial3d/web/src/analysis/runners.js +1644 -0
  34. zero2geospatial3d/web/src/analysis/terrain_results.js +145 -0
  35. zero2geospatial3d/web/src/app.js +630 -0
  36. zero2geospatial3d/web/src/core/acoustics.js +341 -0
  37. zero2geospatial3d/web/src/core/classify.js +158 -0
  38. zero2geospatial3d/web/src/core/comfort.js +686 -0
  39. zero2geospatial3d/web/src/core/daylight_rule.js +199 -0
  40. zero2geospatial3d/web/src/core/dispersion.js +264 -0
  41. zero2geospatial3d/web/src/core/microclimate.js +372 -0
  42. zero2geospatial3d/web/src/core/morphology.js +153 -0
  43. zero2geospatial3d/web/src/core/nsga2.js +181 -0
  44. zero2geospatial3d/web/src/core/party_walls.js +60 -0
  45. zero2geospatial3d/web/src/core/road_sources.js +127 -0
  46. zero2geospatial3d/web/src/core/solar.js +112 -0
  47. zero2geospatial3d/web/src/core/terrain_analysis.js +267 -0
  48. zero2geospatial3d/web/src/core/terrain_math.js +279 -0
  49. zero2geospatial3d/web/src/core/urban_form.js +405 -0
  50. zero2geospatial3d/web/src/core/walkability.js +316 -0
  51. zero2geospatial3d/web/src/core/weather.js +225 -0
  52. zero2geospatial3d/web/src/core/wind.js +131 -0
  53. zero2geospatial3d/web/src/export/check_pdf.js +74 -0
  54. zero2geospatial3d/web/src/export/exporters.js +556 -0
  55. zero2geospatial3d/web/src/export/pdf.js +149 -0
  56. zero2geospatial3d/web/src/export/snapshot.js +234 -0
  57. zero2geospatial3d/web/src/export/snapshot_scene.js +150 -0
  58. zero2geospatial3d/web/src/main.js +75 -0
  59. zero2geospatial3d/web/src/scene/objects.js +645 -0
  60. zero2geospatial3d/web/src/scene/overlay.js +145 -0
  61. zero2geospatial3d/web/src/scene/terrain.js +546 -0
  62. zero2geospatial3d/web/src/scene/viewer.js +275 -0
  63. zero2geospatial3d/web/src/scene/wind_streamlines.js +336 -0
  64. zero2geospatial3d/web/src/style.css +225 -0
  65. zero2geospatial3d/web/src/ui/building_report.js +404 -0
  66. zero2geospatial3d/web/src/ui/charts.js +441 -0
  67. zero2geospatial3d/web/src/ui/colorize.js +101 -0
  68. zero2geospatial3d/web/src/ui/compare.js +362 -0
  69. zero2geospatial3d/web/src/ui/dom.js +104 -0
  70. zero2geospatial3d/web/src/ui/panel_analyze.js +742 -0
  71. zero2geospatial3d/web/src/ui/panel_compare.js +294 -0
  72. zero2geospatial3d/web/src/ui/panel_layers.js +172 -0
  73. zero2geospatial3d/web/src/ui/panel_terrain.js +277 -0
  74. zero2geospatial3d/web/src/ui/qgis_link.js +127 -0
  75. zero2geospatial3d/web/src/ui/sunclock.js +45 -0
  76. zero2geospatial3d/web/src/ui/tools.js +197 -0
  77. zero2geospatial3d/web/src/ui/viewtools.js +173 -0
  78. zero2geospatial3d/web/vendor/three/addons/controls/OrbitControls.js +1417 -0
  79. zero2geospatial3d/web/vendor/three/addons/exporters/GLTFExporter.js +3309 -0
  80. zero2geospatial3d/web/vendor/three/addons/utils/TextureUtils.js +98 -0
  81. zero2geospatial3d/web/vendor/three/three.module.js +53044 -0
  82. zero2geospatial3d/wind.py +178 -0
  83. zero2geospatial3d_sdk-0.1.0.dist-info/METADATA +219 -0
  84. zero2geospatial3d_sdk-0.1.0.dist-info/RECORD +89 -0
  85. zero2geospatial3d_sdk-0.1.0.dist-info/WHEEL +5 -0
  86. zero2geospatial3d_sdk-0.1.0.dist-info/entry_points.txt +2 -0
  87. zero2geospatial3d_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
  88. zero2geospatial3d_sdk-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +52 -0
  89. zero2geospatial3d_sdk-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,173 @@
1
+ // @ts-check
2
+ // Minimal icon bar on the view: image, video, PDF, 3D model / HTML, compare.
3
+ import { h, clear, select, toggle, download, toast } from './dom.js';
4
+ import { renderImage, canvasBlob, exportPdf, exportGlb, exportHtml, Recorder, videoType, fileStem } from '../export/exporters.js';
5
+ import { Compare } from './compare.js';
6
+
7
+ const ICONS = {
8
+ image: '<path d="M4 7h3l2-2.5h6L17 7h3v12H4z"/><circle cx="12" cy="13" r="3.6"/>',
9
+ video: '<rect x="3.5" y="6.5" width="12" height="11" rx="2"/><path d="M15.5 10.5l5-3v9l-5-3z"/>',
10
+ pdf: '<path d="M6 3.5h8.5L19 8v12.5H6z"/><path d="M14.5 3.5V8H19"/><path d="M9 13h7M9 16.5h5"/>',
11
+ model: '<path d="M12 3.5l8 4.5v8l-8 4.5-8-4.5v-8z"/><path d="M12 12.5l8-4.5M12 12.5L4 8M12 12.5v8"/>',
12
+ swipe: '<rect x="3.5" y="5" width="17" height="14" rx="2"/><path d="M12 3v18"/><path d="M8.5 12h-3l1.5-1.5M5.5 12l1.5 1.5M15.5 12h3l-1.5-1.5M18.5 12l-1.5 1.5"/>',
13
+ grid2: '<rect x="3.5" y="5" width="17" height="14" rx="2"/><path d="M12 5v14"/>',
14
+ grid4: '<rect x="3.5" y="5" width="17" height="14" rx="2"/><path d="M12 5v14M3.5 12h17"/>',
15
+ grid6: '<rect x="3.5" y="5" width="17" height="14" rx="2"/><path d="M9.2 5v14M14.8 5v14M3.5 12h17"/>',
16
+ grid8: '<rect x="3.5" y="5" width="17" height="14" rx="2"/><path d="M7.75 5v14M12 5v14M16.25 5v14M3.5 12h17"/>'
17
+ };
18
+
19
+ function icon(name) {
20
+ const span = h('span', { class: 'vt-icon' });
21
+ span.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">${ICONS[name]}</svg>`;
22
+ return span;
23
+ }
24
+
25
+ const SIZES = [['view', 'This view'], ['1920x1080', 'Full HD 1920×1080'], ['2560x1440', 'QHD 2560×1440'], ['3840x2160', '4K 3840×2160'],
26
+ ['7680x4320', '8K 7680×4320'], ['3508x2480', 'A4 300 dpi (landscape)']];
27
+
28
+ function parseSize(v) {
29
+ if (v === 'view') return [0, 0];
30
+ const [w, hh] = v.split('x').map(Number);
31
+ return [w, hh];
32
+ }
33
+
34
+ export function buildViewTools(app) {
35
+ const bar = h('div', { class: 'viewtools' });
36
+ const pop = h('div', { class: 'vt-pop', hidden: true });
37
+ const view = /** @type {HTMLElement} */ (document.getElementById('view'));
38
+ view.append(bar, pop);
39
+ const compare = new Compare(app);
40
+ app.compare = compare;
41
+ const recorder = new Recorder(app);
42
+ const state = { size: 'view', format: 'png', title: true, legend: true, north: true, scale: true, credits: true,
43
+ vmode: 'orbit', seconds: 12, vsize: '1920x1080', fps: 30, paper: 'a4', area: 'scene' };
44
+ let open = null;
45
+ const buttons = {};
46
+
47
+ const show = (key, build) => {
48
+ if (open === key) { close(); return; }
49
+ open = key;
50
+ clear(pop);
51
+ build(pop);
52
+ pop.hidden = false;
53
+ const b = buttons[key].getBoundingClientRect();
54
+ const v = view.getBoundingClientRect();
55
+ pop.style.top = `${b.top - v.top}px`;
56
+ for (const [k, btn] of Object.entries(buttons)) btn.classList.toggle('on', k === key);
57
+ };
58
+ const close = () => {
59
+ open = null;
60
+ pop.hidden = true;
61
+ for (const btn of Object.values(buttons)) btn.classList.remove('on');
62
+ syncCompare();
63
+ };
64
+ const busy = async (label, fn) => {
65
+ toast(`${label} …`);
66
+ try { await fn(); } catch (e) { console.error(e); toast(`${label} failed: ${e.message}`, 'error'); }
67
+ };
68
+ const row = (label, control) => h('label', { class: 'vt-row' }, h('span', { text: label }), control);
69
+ const furniture = () => h('div', { class: 'vt-checks' },
70
+ toggle(state.title, (v) => { state.title = v; }, 'Title'), toggle(state.legend, (v) => { state.legend = v; }, 'Legend'),
71
+ toggle(state.north, (v) => { state.north = v; }, 'North'), toggle(state.scale, (v) => { state.scale = v; }, 'Scale'),
72
+ toggle(state.credits, (v) => { state.credits = v; }, 'Credits'));
73
+ const add = (key, iconName, title, build) => {
74
+ const b = h('button', { class: 'vt-btn', title, onclick: () => show(key, build) }, icon(iconName));
75
+ buttons[key] = b;
76
+ bar.appendChild(b);
77
+ };
78
+
79
+ add('image', 'image', 'Save an image', (p) => {
80
+ p.append(h('div', { class: 'vt-title', text: 'Image' }),
81
+ row('Size', select(SIZES, state.size, (v) => { state.size = v; })),
82
+ row('Format', select([['png', 'PNG'], ['jpeg', 'JPEG']], state.format, (v) => { state.format = v; })),
83
+ furniture(),
84
+ h('div', { class: 'vt-actions' },
85
+ h('button', { class: 'btn primary', text: 'Save', onclick: () => busy('Image', async () => {
86
+ const [w, hh] = parseSize(state.size);
87
+ const canvas = renderImage(app, { width: w, height: hh, furniture: state });
88
+ const type = state.format === 'jpeg' ? 'image/jpeg' : 'image/png';
89
+ download(`${fileStem(app)}.${state.format === 'jpeg' ? 'jpg' : 'png'}`, await canvasBlob(canvas, type));
90
+ toast(`Saved ${canvas.width}×${canvas.height} image.`);
91
+ }) }),
92
+ h('button', { class: 'btn', text: 'Copy', title: 'Copy a PNG of this view to the clipboard', onclick: () => busy('Copy', async () => {
93
+ const canvas = renderImage(app, { furniture: state });
94
+ const blob = await canvasBlob(canvas, 'image/png');
95
+ await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
96
+ toast('Image copied.');
97
+ }) })));
98
+ });
99
+
100
+ add('video', 'video', 'Record a video', (p) => {
101
+ const type = videoType();
102
+ const status = h('div', { class: 'vt-note', text: type ? `Format: ${type.includes('mp4') ? 'MP4' : 'WebM'}` : 'This browser cannot record video.' });
103
+ const startBtn = h('button', { class: 'btn primary', text: recorder.active ? 'Stop' : 'Record', disabled: !type });
104
+ startBtn.onclick = () => {
105
+ if (recorder.active) { recorder.stop(); return; }
106
+ const [w, hh] = parseSize(state.vsize);
107
+ startBtn.textContent = 'Stop';
108
+ bar.classList.add('recording');
109
+ recorder.start({ mode: state.vmode, seconds: state.seconds, fps: state.fps, width: w, height: hh,
110
+ onTick: (f) => { status.textContent = state.vmode === 'free' ? `Recording ${f.toFixed(0)} s …` : `Recording ${Math.round(f * 100)} % …`; } })
111
+ .then((blob) => {
112
+ bar.classList.remove('recording');
113
+ startBtn.textContent = 'Record';
114
+ status.textContent = `Saved ${(blob.size / 1e6).toFixed(1)} MB.`;
115
+ download(`${fileStem(app)}_${state.vmode}.${blob.type.includes('mp4') ? 'mp4' : 'webm'}`, blob);
116
+ })
117
+ .catch((e) => { bar.classList.remove('recording'); startBtn.textContent = 'Record'; toast(e.message, 'error'); });
118
+ };
119
+ p.append(h('div', { class: 'vt-title', text: 'Video' }),
120
+ row('Motion', select([['orbit', '360° orbit'], ['day', 'Sun through the day'], ['free', 'Free (until Stop)']], state.vmode, (v) => { state.vmode = v; })),
121
+ row('Length', select([[8, '8 s'], [12, '12 s'], [20, '20 s'], [30, '30 s']], state.seconds, (v) => { state.seconds = Number(v); })),
122
+ row('Size', select([['view', 'This view'], ['1280x720', '720p'], ['1920x1080', '1080p'], ['3840x2160', '4K']], state.vsize, (v) => { state.vsize = v; })),
123
+ row('Frame rate', select([[30, '30 fps'], [60, '60 fps']], state.fps, (v) => { state.fps = Number(v); })),
124
+ h('div', { class: 'vt-actions' }, startBtn), status);
125
+ });
126
+
127
+ add('pdf', 'pdf', 'Save a PDF sheet', (p) => {
128
+ const title = h('input', { type: 'text', value: app.manifest.title || '' });
129
+ p.append(h('div', { class: 'vt-title', text: 'PDF sheet' }),
130
+ row('Title', title),
131
+ row('Paper', select([['a4', 'A4 landscape'], ['a3', 'A3 landscape']], state.paper, (v) => { state.paper = v; })),
132
+ h('div', { class: 'vt-note', text: 'The view, legend, statistics and notes of the shown result, sun time and CRS.' }),
133
+ h('div', { class: 'vt-actions' }, h('button', { class: 'btn primary', text: 'Save PDF', onclick: () => busy('PDF', async () => {
134
+ download(`${fileStem(app)}.pdf`, await exportPdf(app, { paper: state.paper, title: title.value }));
135
+ toast('PDF saved.');
136
+ }) })));
137
+ });
138
+
139
+ add('model', 'model', '3D model or interactive page', (p) => {
140
+ const areaOf = () => (state.area === 'analysis' ? app.area() : null);
141
+ p.append(h('div', { class: 'vt-title', text: '3D export' }),
142
+ row('Area', select([['scene', 'Whole scene'], ['analysis', 'Analysis area']], state.area, (v) => { state.area = v; })),
143
+ h('div', { class: 'vt-note', text: 'Colours and the shown analysis are baked in. HTML opens anywhere, offline; GLB goes to Blender, SketchUp or the web.' }),
144
+ h('div', { class: 'vt-actions' },
145
+ h('button', { class: 'btn primary', text: 'Interactive HTML', onclick: () => busy('HTML', async () => {
146
+ const blob = await exportHtml(app, { area: areaOf() });
147
+ download(`${fileStem(app)}_3d.html`, blob);
148
+ toast(`HTML saved (${(blob.size / 1e6).toFixed(1)} MB).`);
149
+ }) }),
150
+ h('button', { class: 'btn', text: 'GLB model', onclick: () => busy('GLB', async () => {
151
+ const blob = await exportGlb(app, { area: areaOf() });
152
+ download(`${fileStem(app)}.glb`, blob);
153
+ toast(`GLB saved (${(blob.size / 1e6).toFixed(1)} MB).`);
154
+ }) })));
155
+ });
156
+
157
+ bar.appendChild(h('div', { class: 'vt-sep' }));
158
+ const cmp = {};
159
+ for (const [mode, ic, title] of [['swipe', 'swipe', 'Swipe between two views'], [2, 'grid2', '2 synced panels'], [4, 'grid4', '4 synced panels'],
160
+ [6, 'grid6', '6 synced panels'], [8, 'grid8', '8 synced panels']]) {
161
+ const b = h('button', { class: 'vt-btn', title, onclick: () => { close(); compare.toggle(mode); syncCompare(); } }, icon(ic));
162
+ cmp[mode] = b;
163
+ bar.appendChild(b);
164
+ }
165
+ function syncCompare() {
166
+ for (const [k, b] of Object.entries(cmp)) b.classList.toggle('on', String(compare.mode) === k);
167
+ }
168
+ app.on((what) => { if (what === 'compare') syncCompare(); }); // also opened from the panels
169
+ window.addEventListener('resize', () => compare.onResize());
170
+ if ('ResizeObserver' in window) new ResizeObserver(() => compare.onResize()).observe(view);
171
+ window.addEventListener('keydown', (e) => { if (e.key === 'Escape' && open) close(); });
172
+ return { compare };
173
+ }