@jjlmoya/utils-drones 1.16.0 → 1.17.0

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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/entries.ts +4 -1
  3. package/src/index.ts +1 -0
  4. package/src/tests/locale_completeness.test.ts +2 -2
  5. package/src/tests/tool_validation.test.ts +2 -2
  6. package/src/tool/drone-power-analyzer/i18n/de.ts +1 -1
  7. package/src/tool/drone-power-analyzer/i18n/es.ts +1 -1
  8. package/src/tool/drone-power-analyzer/i18n/fr.ts +1 -1
  9. package/src/tool/gsd-flight-planner/InputsColumn.astro +108 -0
  10. package/src/tool/gsd-flight-planner/ResultsColumn.astro +90 -0
  11. package/src/tool/gsd-flight-planner/bibliography.astro +6 -0
  12. package/src/tool/gsd-flight-planner/bibliography.ts +7 -0
  13. package/src/tool/gsd-flight-planner/component.astro +22 -0
  14. package/src/tool/gsd-flight-planner/entry.ts +29 -0
  15. package/src/tool/gsd-flight-planner/gsd-flight-planner.css +515 -0
  16. package/src/tool/gsd-flight-planner/i18n/de.ts +214 -0
  17. package/src/tool/gsd-flight-planner/i18n/en.ts +214 -0
  18. package/src/tool/gsd-flight-planner/i18n/es.ts +221 -0
  19. package/src/tool/gsd-flight-planner/i18n/fr.ts +214 -0
  20. package/src/tool/gsd-flight-planner/i18n/id.ts +214 -0
  21. package/src/tool/gsd-flight-planner/i18n/it.ts +214 -0
  22. package/src/tool/gsd-flight-planner/i18n/ja.ts +214 -0
  23. package/src/tool/gsd-flight-planner/i18n/ko.ts +214 -0
  24. package/src/tool/gsd-flight-planner/i18n/nl.ts +214 -0
  25. package/src/tool/gsd-flight-planner/i18n/pl.ts +214 -0
  26. package/src/tool/gsd-flight-planner/i18n/pt.ts +214 -0
  27. package/src/tool/gsd-flight-planner/i18n/ru.ts +214 -0
  28. package/src/tool/gsd-flight-planner/i18n/sv.ts +214 -0
  29. package/src/tool/gsd-flight-planner/i18n/tr.ts +214 -0
  30. package/src/tool/gsd-flight-planner/i18n/zh.ts +214 -0
  31. package/src/tool/gsd-flight-planner/index.ts +8 -0
  32. package/src/tool/gsd-flight-planner/logic.js +326 -0
  33. package/src/tool/gsd-flight-planner/seo.astro +15 -0
  34. package/src/tools.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-drones",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/entries.ts CHANGED
@@ -6,9 +6,12 @@ export { gpsCoordinatesConverter } from './tool/gps-coordinates-converter/entry'
6
6
  export type { GpsCoordinatesConverterUI, GpsCoordinatesConverterLocaleContent } from './tool/gps-coordinates-converter/entry';
7
7
  export { dronePowerAnalyzer } from './tool/drone-power-analyzer/entry';
8
8
  export type { DronePowerAnalyzerUI, DronePowerAnalyzerLocaleContent } from './tool/drone-power-analyzer/entry';
9
+ export { gsdFlightPlanner } from './tool/gsd-flight-planner/entry';
10
+ export type { GsdFlightPlannerUI, GsdFlightPlannerLocaleContent } from './tool/gsd-flight-planner/entry';
9
11
  export { dronesCategory } from './category';
10
12
  import { antennaLengthCalculator } from './tool/antenna-length-calculator/entry';
11
13
  import { droneFlightTime } from './tool/drone-flight-time/entry';
12
14
  import { gpsCoordinatesConverter } from './tool/gps-coordinates-converter/entry';
13
15
  import { dronePowerAnalyzer } from './tool/drone-power-analyzer/entry';
14
- export const ALL_ENTRIES = [antennaLengthCalculator, droneFlightTime, gpsCoordinatesConverter, dronePowerAnalyzer];
16
+ import { gsdFlightPlanner } from './tool/gsd-flight-planner/entry';
17
+ export const ALL_ENTRIES = [antennaLengthCalculator, droneFlightTime, gpsCoordinatesConverter, dronePowerAnalyzer, gsdFlightPlanner];
package/src/index.ts CHANGED
@@ -20,4 +20,5 @@ export { DRONE_FLIGHT_TIME_TOOL } from './tool/drone-flight-time/index';
20
20
  export { ANTENNA_LENGTH_CALCULATOR_TOOL } from './tool/antenna-length-calculator/index';
21
21
  export { GPS_COORDINATES_CONVERTER_TOOL } from './tool/gps-coordinates-converter/index';
22
22
  export { DRONE_POWER_ANALYZER_TOOL } from './tool/drone-power-analyzer/index';
23
+ export { GSD_FLIGHT_PLANNER_TOOL } from './tool/gsd-flight-planner/index';
23
24
 
@@ -17,8 +17,8 @@ describe('Locale Completeness Validation', () => {
17
17
  });
18
18
  });
19
19
 
20
- it('all 4 tools registered', () => {
21
- expect(ALL_TOOLS.length).toBe(4);
20
+ it('all 5 tools registered', () => {
21
+ expect(ALL_TOOLS.length).toBe(5);
22
22
  });
23
23
  });
24
24
 
@@ -4,8 +4,8 @@ import { dronesCategory } from '../data';
4
4
 
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
- it('should have 4 tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(4);
7
+ it('should have 5 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(5);
9
9
  });
10
10
 
11
11
  it('dronesCategory should be defined', () => {
@@ -2,7 +2,7 @@ import type { DronePowerAnalyzerLocaleContent } from '../index';
2
2
  import type { WithContext, SoftwareApplication, FAQPage, HowTo } from 'schema-dts';
3
3
  import { bibliography } from '../bibliography';
4
4
 
5
- const slug = 'drohnen-leistungsanalyzer';
5
+ const slug = 'drohnen-leistungs-analysator';
6
6
  const title = 'Drohnen Leistungsanalysator: Schub und Gewichts Verhältnis Rechner für FPV';
7
7
  const description = 'Berechnen Sie das kritische Schub-Gewichts-Verhältnis für Ihren FPV-Drohnenbau. Erhalten Sie sofortige Flugprofil-Empfehlungen, eine visuelle Leistungsanzeige und optimieren Sie für Cinematic, Freestyle oder Racing.';
8
8
 
@@ -3,7 +3,7 @@ import type { WithContext, SoftwareApplication, FAQPage, HowTo } from 'schema-dt
3
3
  import { bibliography } from '../bibliography';
4
4
 
5
5
  const slug = 'analizador-potencia-dron';
6
- const title = 'Analizador de Potencia de Drone: Calculadora de Relación Empuje y Peso para FPV';
6
+ const title = 'Analizador de Potencia de Dron: Calculadora de Relación Empuje y Peso para FPV';
7
7
  const description = 'Calcula la relación empuje-peso crítica para tu montaje de drone FPV. Obtén recomendaciones instantáneas de perfil de vuelo, medidor de potencia visual y optimiza para cine, freestyle o carreras.';
8
8
 
9
9
  const faqItems = [
@@ -2,7 +2,7 @@ import type { DronePowerAnalyzerLocaleContent } from '../index';
2
2
  import type { WithContext, SoftwareApplication, FAQPage, HowTo } from 'schema-dts';
3
3
  import { bibliography } from '../bibliography';
4
4
 
5
- const slug = 'analyseur-puissance-dron';
5
+ const slug = 'analyseur-puissance-drone';
6
6
  const title = 'Analyseur de Puissance de Drone : Calculateur de Rapport Poussée/Poids pour FPV';
7
7
  const description = 'Calculez le rapport poussée/poids critique pour votre drone FPV. Obtenez des recommandations de profil de vol instantanées, une jauge de puissance visuelle et optimisez pour le cinématique, le freestyle ou la course.';
8
8
 
@@ -0,0 +1,108 @@
1
+ ---
2
+ const { ui } = Astro.props;
3
+ ---
4
+
5
+ <div class="column column-inputs">
6
+ <h2 class="column-title">{ui.configuration || "Configuration"}</h2>
7
+
8
+ <div class="section">
9
+ <label class="section-label">{ui.cameraSelection || "Camera Selection"}</label>
10
+ <select id="cameraPreset" class="input-field">
11
+ <option value="">{ui.manualMode || "Manual Mode"}</option>
12
+ <option value="mavic3e">{ui.presetDjiMavic3e || "DJI Mavic 3E"}</option>
13
+ <option value="matrice300">{ui.presetDjiMatrice300 || "DJI Matrice 300 RTK"}</option>
14
+ <option value="autelevoii">{ui.presetAutelEvoII || "Autel EVO II"}</option>
15
+ <option value="air3s">{ui.presetDjiAir3s || "DJI Air 3S"}</option>
16
+ </select>
17
+ </div>
18
+
19
+ <div class="section">
20
+ <label class="section-label">{ui.sensorConfig || "Sensor Configuration"}</label>
21
+ <div class="input-row">
22
+ <div class="input-item">
23
+ <span class="input-label">{ui.width || "Width"}</span>
24
+ <input id="sensorWidth" type="number" min="1" max="100" step="0.1" value="23.5" class="input-field" />
25
+ <span class="unit-hint">mm</span>
26
+ </div>
27
+ <div class="input-item">
28
+ <span class="input-label">{ui.height || "Height"}</span>
29
+ <input id="sensorHeight" type="number" min="1" max="100" step="0.1" value="15.6" class="input-field" />
30
+ <span class="unit-hint">mm</span>
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ <div class="section">
36
+ <label class="section-label">{ui.focalLength || "Focal Length"}</label>
37
+ <div class="input-item">
38
+ <input id="focalLength" type="number" min="1" max="1000" step="0.1" value="35" class="input-field" />
39
+ <span class="unit-hint" id="focalLengthUnit">mm</span>
40
+ </div>
41
+ </div>
42
+
43
+ <div class="section">
44
+ <label class="section-label">{ui.imageResolution || "Image Resolution"}</label>
45
+ <div class="input-row">
46
+ <div class="input-item">
47
+ <span class="input-label">{ui.w || "W"}</span>
48
+ <input id="imageWidth" type="number" min="100" max="50000" step="1" value="5472" class="input-field" />
49
+ <span class="unit-hint">{ui.px || "px"}</span>
50
+ </div>
51
+ <div class="input-item">
52
+ <span class="input-label">{ui.h || "H"}</span>
53
+ <input id="imageHeight" type="number" min="100" max="50000" step="1" value="3648" class="input-field" />
54
+ <span class="unit-hint">{ui.px || "px"}</span>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <div class="section">
60
+ <label class="section-label">{ui.altitudeAgl || "Altitude (AGL)"}</label>
61
+ <div class="input-item">
62
+ <input id="altitudeAgl" type="number" min="10" max="500" step="5" value="100" class="input-field" />
63
+ <span class="unit-hint" id="altUnit">m</span>
64
+ </div>
65
+ <input id="altitudeSlider" type="range" min="10" max="500" step="5" value="100" class="slider" />
66
+ </div>
67
+
68
+ <div class="section">
69
+ <label class="section-label">{ui.overlapSettings || "Overlap Configuration"}</label>
70
+ <div class="input-row">
71
+ <div class="input-item">
72
+ <span class="input-label">{ui.forward || "Forward"}</span>
73
+ <input id="forwardOverlap" type="number" min="0" max="95" step="5" value="75" class="input-field" />
74
+ <span class="unit-hint">%</span>
75
+ </div>
76
+ <div class="input-item">
77
+ <span class="input-label">{ui.lateral || "Lateral"}</span>
78
+ <input id="lateralOverlap" type="number" min="0" max="95" step="5" value="65" class="input-field" />
79
+ <span class="unit-hint">%</span>
80
+ </div>
81
+ </div>
82
+ </div>
83
+
84
+ <div class="section">
85
+ <label class="section-label">{ui.missionArea || "Mission Area"}</label>
86
+ <div class="input-item">
87
+ <span class="input-label">{ui.totalAreaToSurvey || "Total Area to Survey"}</span>
88
+ <input id="missionArea" type="number" min="0.1" max="10000000" step="0.1" value="100" class="input-field" />
89
+ <span class="unit-hint" id="areaInputUnit">m²</span>
90
+ <span class="unit-hint" style="font-size: 0.65rem; margin-top: 0.25rem;">{ui.hectareHint || "1 ha = 10,000 m²"}</span>
91
+ </div>
92
+ </div>
93
+
94
+ <div class="section">
95
+ <label class="section-label">{ui.inverseCalc || "Reverse Calculation"}</label>
96
+ <div class="input-item">
97
+ <span class="input-label">{ui.targetGsd || "Target GSD"}</span>
98
+ <input id="desiredGsd" type="number" min="0.1" max="50" step="0.1" value="2" class="input-field" />
99
+ <span class="unit-hint" id="desiredGsdUnit">cm/px</span>
100
+ </div>
101
+ <div class="result-box">
102
+ <span class="result-label">{ui.maxAltitude || "Max Altitude"}</span>
103
+ <span class="result-value" id="maxAltResult">0.00 m</span>
104
+ </div>
105
+ </div>
106
+
107
+ <button id="resetBtn" class="btn-secondary">{ui.reset || "Reset"}</button>
108
+ </div>
@@ -0,0 +1,90 @@
1
+ ---
2
+ const { ui } = Astro.props;
3
+ ---
4
+
5
+ <div class="column column-results">
6
+ <h2 class="column-title">{ui.results || "Results"}</h2>
7
+
8
+ <div class="result-section">
9
+ <div class="result-header">{ui.gsdResult || "Ground Sample Distance"}</div>
10
+ <div class="gsd-large">
11
+ <div class="gsd-number" id="gsdNumberLarge">0.00</div>
12
+ <div class="gsd-label" id="gsdUnitLabel">{ui.gsdUnit || "cm/px"}</div>
13
+ </div>
14
+
15
+ <div class="precision-bar">
16
+ <div class="bar-track">
17
+ <div class="bar-fill" id="scaleFill"></div>
18
+ </div>
19
+ <div class="bar-labels">
20
+ <span class="bar-label-item" title={ui.classHighPrecision}>{ui.highPrec || "High Prec."}</span>
21
+ <span class="bar-label-item" title={ui.classStandard}>{ui.standard || "Standard"}</span>
22
+ <span class="bar-label-item" title={ui.classInspection}>{ui.inspection || "Inspection"}</span>
23
+ <span class="bar-label-item" title={ui.classVisual}>{ui.visual || "Visual"}</span>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="classification" id="classificationBadge">—</div>
28
+ </div>
29
+
30
+ <div class="result-section">
31
+ <div class="result-header">{ui.coveragePerImage || "Coverage per Image"}</div>
32
+ <div class="coverage-display">
33
+ <div class="coverage-stat">
34
+ <span class="stat-label">{ui.area || "Area"}</span>
35
+ <span class="stat-value" id="areaDisplay">0</span>
36
+ <span class="stat-unit" id="areaUnit">m²</span>
37
+ </div>
38
+ <div class="coverage-stat">
39
+ <span class="stat-label">{ui.spacing || "Spacing"}</span>
40
+ <span class="stat-value" id="spacingDisplay">0</span>
41
+ <span class="stat-unit" id="spacingUnit">m</span>
42
+ </div>
43
+ </div>
44
+
45
+ <svg class="coverage-visual" viewBox="0 0 220 100">
46
+ <defs>
47
+ <pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse">
48
+ <path d="M 10 0 L 0 0 0 10" fill="none" stroke="currentColor" stroke-width="0.5" opacity="0.1"/>
49
+ </pattern>
50
+ </defs>
51
+ <rect width="220" height="100" fill="url(#grid)"/>
52
+ <text x="10" y="95" font-size="8" opacity="0.4">{ui.flightDir || "Flight →"}</text>
53
+ <rect id="photo1Rect" x="20" y="30" width="80" height="40" fill="currentColor" opacity="0.25" stroke="currentColor" stroke-width="1.5" stroke-dasharray="2,2"/>
54
+ <rect id="photo2Rect" x="70" y="30" width="80" height="40" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="1.5"/>
55
+ <rect id="overlapRect" x="70" y="30" width="30" height="40" fill="currentColor" opacity="0.4" stroke="none"/>
56
+ <text id="overlapText" x="85" y="58" font-size="9" text-anchor="middle" fill="currentColor" font-weight="bold" opacity="0.8">70%</text>
57
+ </svg>
58
+ </div>
59
+
60
+ <div class="result-section">
61
+ <div class="result-header">{ui.missionMetrics || "Mission Metrics"}</div>
62
+ <div class="metrics-display">
63
+ <div class="metric-item">
64
+ <span class="metric-label">{ui.images || "Images"}</span>
65
+ <span class="metric-number" id="totalImages">0</span>
66
+ <span class="metric-unit">{ui.shots || "shots"}</span>
67
+ </div>
68
+ <div class="metric-item">
69
+ <span class="metric-label">{ui.flightLines || "Flight Lines"}</span>
70
+ <span class="metric-number" id="flightLines">0</span>
71
+ <span class="metric-unit">{ui.lines || "lines"}</span>
72
+ </div>
73
+ <div class="metric-item">
74
+ <span class="metric-label">{ui.flightTime || "Flight Time"}</span>
75
+ <span class="metric-number" id="flightTime">0</span>
76
+ <span class="metric-unit">{ui.min || "min"}</span>
77
+ </div>
78
+ <div class="metric-item">
79
+ <span class="metric-label">{ui.dataVolume || "Data Volume"}</span>
80
+ <span class="metric-number" id="dataVolume">0</span>
81
+ <span class="metric-unit">{ui.gb || "GB"}</span>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <div id="alertsContainer" class="alerts-container"></div>
87
+
88
+ <button id="shareBtn" class="btn-primary" style="margin-bottom: 0.75rem;">{ui.copyShareLink || "Copy Share Link"}</button>
89
+ <button id="exportBtn" class="btn-secondary">{ui.downloadReport || "Download Report"}</button>
90
+ </div>
@@ -0,0 +1,6 @@
1
+ ---
2
+ import { Bibliography as BibliographyComponent } from '@jjlmoya/utils-shared';
3
+ import { bibliography } from './bibliography';
4
+ ---
5
+
6
+ <BibliographyComponent links={bibliography} />
@@ -0,0 +1,7 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ { name: 'Pix4D - Understanding Ground Sample Distance (GSD)', url: 'https://support.pix4d.com/hc/en-us/articles/202559809' },
5
+ { name: 'DJI - Photogrammetry Basics & GSD Calculator', url: 'https://enterprise-insights.dji.com/blog/ground-sample-distance' },
6
+ { name: 'OpenDroneMap - Flight Planning Guide', url: 'https://docs.opendronemap.org/flying/' }
7
+ ];
@@ -0,0 +1,22 @@
1
+ ---
2
+ import InputsColumn from './InputsColumn.astro';
3
+ import ResultsColumn from './ResultsColumn.astro';
4
+ const { ui } = Astro.props;
5
+ ---
6
+
7
+ <div class="gsd-flight-planner-ui" data-ui={JSON.stringify(ui)}>
8
+ <div class="main-card">
9
+ <div class="unit-toggle unit-toggle-corner">
10
+ <button id="metricBtn" class="unit-btn active">{ui.metric || "Metric"}</button>
11
+ <button id="imperialBtn" class="unit-btn">{ui.imperial || "Imperial"}</button>
12
+ </div>
13
+ <InputsColumn {ui} />
14
+ <div class="divider"></div>
15
+ <ResultsColumn {ui} />
16
+ </div>
17
+ </div>
18
+
19
+ <script is:inline define:vars={{ ui }}>
20
+ window.ui = ui;
21
+ </script>
22
+ <script src="./logic.js"></script>
@@ -0,0 +1,29 @@
1
+ import type { DronesToolEntry, ToolLocaleContent } from '../../types';
2
+
3
+ export interface GsdFlightPlannerUI {
4
+ [key: string]: string;
5
+ }
6
+
7
+ export type GsdFlightPlannerLocaleContent = ToolLocaleContent<GsdFlightPlannerUI>;
8
+
9
+ export const gsdFlightPlanner: DronesToolEntry<GsdFlightPlannerUI> = {
10
+ id: 'gsd-flight-planner',
11
+ icons: { bg: 'mdi:drone', fg: 'mdi:map-marker-distance' },
12
+ i18n: {
13
+ es: () => import('./i18n/es').then((m) => m.content),
14
+ en: () => import('./i18n/en').then((m) => m.content),
15
+ fr: () => import('./i18n/fr').then((m) => m.content),
16
+ de: () => import('./i18n/de').then((m) => m.content),
17
+ it: () => import('./i18n/it').then((m) => m.content),
18
+ pt: () => import('./i18n/pt').then((m) => m.content),
19
+ nl: () => import('./i18n/nl').then((m) => m.content),
20
+ pl: () => import('./i18n/pl').then((m) => m.content),
21
+ ru: () => import('./i18n/ru').then((m) => m.content),
22
+ ja: () => import('./i18n/ja').then((m) => m.content),
23
+ ko: () => import('./i18n/ko').then((m) => m.content),
24
+ zh: () => import('./i18n/zh').then((m) => m.content),
25
+ tr: () => import('./i18n/tr').then((m) => m.content),
26
+ sv: () => import('./i18n/sv').then((m) => m.content),
27
+ id: () => import('./i18n/id').then((m) => m.content),
28
+ },
29
+ };