@jjlmoya/utils-drones 1.30.0 → 1.32.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 (67) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/en.ts +1 -1
  3. package/src/category/i18n/fr.ts +1 -1
  4. package/src/category/index.ts +2 -0
  5. package/src/components/PreviewNavSidebar.astro +116 -116
  6. package/src/components/PreviewToolbar.astro +143 -143
  7. package/src/data.ts +1 -1
  8. package/src/entries.ts +4 -0
  9. package/src/env.d.ts +1 -1
  10. package/src/index.ts +1 -1
  11. package/src/layouts/PreviewLayout.astro +118 -118
  12. package/src/pages/[locale].astro +251 -251
  13. package/src/pages/index.astro +4 -4
  14. package/src/tests/faq_count.test.ts +1 -1
  15. package/src/tests/locale_completeness.test.ts +1 -2
  16. package/src/tests/mocks/astro_mock.js +1 -1
  17. package/src/tests/no_h1_in_components.test.ts +1 -1
  18. package/src/tests/tool_validation.test.ts +17 -17
  19. package/src/tool/antenna-length-calculator/bibliography.astro +6 -6
  20. package/src/tool/antenna-length-calculator/component.astro +329 -329
  21. package/src/tool/antenna-length-calculator/seo.astro +15 -15
  22. package/src/tool/drone-battery-c-rating-calculator/i18n/de.ts +192 -192
  23. package/src/tool/drone-battery-c-rating-calculator/i18n/en.ts +192 -192
  24. package/src/tool/drone-battery-c-rating-calculator/i18n/es.ts +192 -192
  25. package/src/tool/drone-battery-c-rating-calculator/i18n/id.ts +192 -192
  26. package/src/tool/drone-battery-c-rating-calculator/i18n/ja.ts +192 -192
  27. package/src/tool/drone-battery-c-rating-calculator/i18n/ko.ts +192 -192
  28. package/src/tool/drone-battery-c-rating-calculator/i18n/nl.ts +192 -192
  29. package/src/tool/drone-battery-c-rating-calculator/i18n/pl.ts +192 -192
  30. package/src/tool/drone-battery-c-rating-calculator/i18n/ru.ts +192 -192
  31. package/src/tool/drone-battery-c-rating-calculator/i18n/sv.ts +192 -192
  32. package/src/tool/drone-battery-c-rating-calculator/i18n/tr.ts +192 -192
  33. package/src/tool/drone-battery-c-rating-calculator/i18n/zh.ts +192 -192
  34. package/src/tool/drone-flight-time/component.astro +187 -187
  35. package/src/tool/drone-flight-time/seo.astro +15 -15
  36. package/src/tool/drone-motor-propeller-calculator/bibliography.astro +6 -0
  37. package/src/tool/drone-motor-propeller-calculator/bibliography.ts +12 -0
  38. package/src/tool/drone-motor-propeller-calculator/component.astro +109 -0
  39. package/src/tool/drone-motor-propeller-calculator/controller.ts +174 -0
  40. package/src/tool/drone-motor-propeller-calculator/dom-views.ts +78 -0
  41. package/src/tool/drone-motor-propeller-calculator/drone-motor-propeller-calculator.css +562 -0
  42. package/src/tool/drone-motor-propeller-calculator/entry.ts +27 -0
  43. package/src/tool/drone-motor-propeller-calculator/evaluator.ts +14 -0
  44. package/src/tool/drone-motor-propeller-calculator/i18n/de.ts +202 -0
  45. package/src/tool/drone-motor-propeller-calculator/i18n/en.ts +202 -0
  46. package/src/tool/drone-motor-propeller-calculator/i18n/es.ts +202 -0
  47. package/src/tool/drone-motor-propeller-calculator/i18n/fr.ts +202 -0
  48. package/src/tool/drone-motor-propeller-calculator/i18n/id.ts +192 -0
  49. package/src/tool/drone-motor-propeller-calculator/i18n/it.ts +192 -0
  50. package/src/tool/drone-motor-propeller-calculator/i18n/ja.ts +192 -0
  51. package/src/tool/drone-motor-propeller-calculator/i18n/ko.ts +192 -0
  52. package/src/tool/drone-motor-propeller-calculator/i18n/nl.ts +192 -0
  53. package/src/tool/drone-motor-propeller-calculator/i18n/pl.ts +192 -0
  54. package/src/tool/drone-motor-propeller-calculator/i18n/pt.ts +192 -0
  55. package/src/tool/drone-motor-propeller-calculator/i18n/ru.ts +192 -0
  56. package/src/tool/drone-motor-propeller-calculator/i18n/sv.ts +192 -0
  57. package/src/tool/drone-motor-propeller-calculator/i18n/tr.ts +192 -0
  58. package/src/tool/drone-motor-propeller-calculator/i18n/zh.ts +192 -0
  59. package/src/tool/drone-motor-propeller-calculator/index.ts +11 -0
  60. package/src/tool/drone-motor-propeller-calculator/logic.test.ts +56 -0
  61. package/src/tool/drone-motor-propeller-calculator/logic.ts +108 -0
  62. package/src/tool/drone-motor-propeller-calculator/seo.astro +15 -0
  63. package/src/tool/drone-motor-propeller-calculator/storage.ts +26 -0
  64. package/src/tool/drone-motor-propeller-calculator/ui.ts +55 -0
  65. package/src/tool/gps-coordinates-converter/component.astro +275 -275
  66. package/src/tool/gps-coordinates-converter/seo.astro +15 -15
  67. package/src/tools.ts +3 -0
@@ -0,0 +1,108 @@
1
+ export type BladeCount = 2 | 3 | 4;
2
+ export type MotorCount = 2 | 4 | 6 | 8;
3
+ export type ThrustSource = 'model' | 'bench';
4
+
5
+ export interface DroneMotorInputs {
6
+ motorKv: number;
7
+ batteryVoltage: number;
8
+ propDiameterInches: number;
9
+ propPitchInches: number;
10
+ bladeCount: BladeCount;
11
+ motorCount: MotorCount;
12
+ droneWeightGrams: number;
13
+ benchThrustGrams?: number | undefined;
14
+ benchVoltage?: number | undefined;
15
+ }
16
+
17
+ export interface DroneMotorResults {
18
+ loadedRpm: number;
19
+ pitchSpeedKmh: number;
20
+ thrustPerMotorGrams: number;
21
+ totalThrustGrams: number;
22
+ powerPerMotorWatts: number;
23
+ totalPowerWatts: number;
24
+ currentPerMotorAmps: number;
25
+ totalCurrentAmps: number;
26
+ thrustToWeight: number;
27
+ thrustMarginPercent: number;
28
+ hoverThrottlePercent: number;
29
+ thrustSource: ThrustSource;
30
+ }
31
+
32
+ const AIR_DENSITY_KG_PER_M3 = 1.225;
33
+ const GRAM_FORCE_TO_NEWTON = 0.00980665;
34
+ const INCH_TO_METRE = 0.0254;
35
+ const LOADED_RPM_FACTOR = 0.85;
36
+ const MOTOR_EFFICIENCY = 0.87;
37
+
38
+ function clamp(value: number, minimum: number, maximum: number): number {
39
+ return Math.min(Math.max(value, minimum), maximum);
40
+ }
41
+
42
+ function getBladeFactor(bladeCount: BladeCount): number {
43
+ const factors: Record<BladeCount, number> = { 2: 1, 3: 1.08, 4: 1.14 };
44
+ return factors[bladeCount];
45
+ }
46
+
47
+ function getPitchFactor(inputs: DroneMotorInputs): number {
48
+ const ratio = clamp(inputs.propPitchInches / inputs.propDiameterInches, 0.2, 1.2);
49
+ return clamp(0.72 + ratio * 0.45, 0.75, 1.26);
50
+ }
51
+
52
+ export function calculateLoadedRpm(inputs: DroneMotorInputs): number {
53
+ return inputs.motorKv * inputs.batteryVoltage * LOADED_RPM_FACTOR;
54
+ }
55
+
56
+ function calculateModelThrust(inputs: DroneMotorInputs, loadedRpm: number): number {
57
+ const diameter = inputs.propDiameterInches * INCH_TO_METRE;
58
+ const rotationsPerSecond = loadedRpm / 60;
59
+ const thrustCoefficient = 0.105 * getBladeFactor(inputs.bladeCount) * getPitchFactor(inputs);
60
+ const thrustNewtons = thrustCoefficient * AIR_DENSITY_KG_PER_M3 * rotationsPerSecond ** 2 * diameter ** 4;
61
+ return Math.max(thrustNewtons / GRAM_FORCE_TO_NEWTON, 0);
62
+ }
63
+
64
+ function calculateBenchThrust(inputs: DroneMotorInputs, loadedRpm: number): number | undefined {
65
+ if (!inputs.benchThrustGrams || !inputs.benchVoltage || inputs.benchVoltage <= 0) return undefined;
66
+ const referenceRpm = inputs.motorKv * inputs.benchVoltage;
67
+ const rpmRatio = loadedRpm / referenceRpm;
68
+ return Math.max(inputs.benchThrustGrams * rpmRatio ** 2, 0);
69
+ }
70
+
71
+ function calculateThrust(inputs: DroneMotorInputs, loadedRpm: number): { grams: number; source: ThrustSource } {
72
+ const benchThrust = calculateBenchThrust(inputs, loadedRpm);
73
+ if (benchThrust !== undefined) return { grams: benchThrust, source: 'bench' };
74
+ return { grams: calculateModelThrust(inputs, loadedRpm), source: 'model' };
75
+ }
76
+
77
+ function calculatePower(inputs: DroneMotorInputs, loadedRpm: number): number {
78
+ const diameter = inputs.propDiameterInches * INCH_TO_METRE;
79
+ const rotationsPerSecond = loadedRpm / 60;
80
+ const pitchFactor = getPitchFactor(inputs);
81
+ const powerCoefficient = 0.045 * getBladeFactor(inputs.bladeCount) * (0.86 + pitchFactor * 0.16);
82
+ const shaftPower = powerCoefficient * AIR_DENSITY_KG_PER_M3 * rotationsPerSecond ** 3 * diameter ** 5;
83
+ return Math.max(shaftPower / MOTOR_EFFICIENCY, 0);
84
+ }
85
+
86
+ export function calculateDroneMotorMetrics(inputs: DroneMotorInputs): DroneMotorResults {
87
+ const loadedRpm = calculateLoadedRpm(inputs);
88
+ const thrust = calculateThrust(inputs, loadedRpm);
89
+ const totalThrustGrams = thrust.grams * inputs.motorCount;
90
+ const powerPerMotorWatts = calculatePower(inputs, loadedRpm);
91
+ const totalPowerWatts = powerPerMotorWatts * inputs.motorCount;
92
+ const totalCurrentAmps = totalPowerWatts / inputs.batteryVoltage;
93
+ const thrustToWeight = totalThrustGrams / inputs.droneWeightGrams;
94
+ return {
95
+ loadedRpm,
96
+ pitchSpeedKmh: loadedRpm * inputs.propPitchInches * INCH_TO_METRE * 60 / 1000,
97
+ thrustPerMotorGrams: thrust.grams,
98
+ totalThrustGrams,
99
+ powerPerMotorWatts,
100
+ totalPowerWatts,
101
+ currentPerMotorAmps: totalCurrentAmps / inputs.motorCount,
102
+ totalCurrentAmps,
103
+ thrustToWeight,
104
+ thrustMarginPercent: (thrustToWeight - 1) * 100,
105
+ hoverThrottlePercent: clamp(Math.sqrt(1 / thrustToWeight) * 100, 0, 100),
106
+ thrustSource: thrust.source,
107
+ };
108
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import type { KnownLocale } from '../../types';
4
+ import { droneMotorPropellerCalculator } from './entry';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await droneMotorPropellerCalculator.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,26 @@
1
+ import type { DroneMotorInputs } from './logic';
2
+
3
+ export type DisplayUnit = 'metric' | 'imperial';
4
+
5
+ const STORAGE_KEY = 'jjlmoya-drone-motor-propeller-calculator';
6
+
7
+ interface StoredState {
8
+ inputs: DroneMotorInputs;
9
+ unit: DisplayUnit;
10
+ }
11
+
12
+ export function readStoredState(): Partial<StoredState> {
13
+ try {
14
+ const raw = window.localStorage.getItem(STORAGE_KEY);
15
+ if (!raw) return {};
16
+ return JSON.parse(raw) as Partial<StoredState>;
17
+ } catch {
18
+ return {};
19
+ }
20
+ }
21
+
22
+ export function writeStoredState(state: StoredState): void {
23
+ try {
24
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
25
+ } catch {}
26
+ }
@@ -0,0 +1,55 @@
1
+ export interface DroneMotorPropellerUI extends Record<string, string> {
2
+ presetsHeader: string;
3
+ presetTinyCruiser: string;
4
+ presetFreestyle: string;
5
+ presetLongRange: string;
6
+ presetCinelifter: string;
7
+ unitHeader: string;
8
+ metricUnit: string;
9
+ imperialUnit: string;
10
+ setupHeader: string;
11
+ motorKvLabel: string;
12
+ batteryVoltageLabel: string;
13
+ propDiameterLabel: string;
14
+ propPitchLabel: string;
15
+ bladeCountLabel: string;
16
+ motorCountLabel: string;
17
+ droneWeightLabel: string;
18
+ benchDataHeader: string;
19
+ benchThrustLabel: string;
20
+ benchVoltageLabel: string;
21
+ optionalLabel: string;
22
+ twoBlades: string;
23
+ threeBlades: string;
24
+ fourBlades: string;
25
+ twoMotors: string;
26
+ fourMotors: string;
27
+ sixMotors: string;
28
+ eightMotors: string;
29
+ resultsHeader: string;
30
+ estimatedLabel: string;
31
+ benchBasedLabel: string;
32
+ loadedRpmLabel: string;
33
+ pitchSpeedLabel: string;
34
+ thrustPerMotorLabel: string;
35
+ totalThrustLabel: string;
36
+ totalPowerLabel: string;
37
+ totalCurrentLabel: string;
38
+ thrustMarginLabel: string;
39
+ hoverThrottleLabel: string;
40
+ sceneCaption: string;
41
+ underpoweredStatus: string;
42
+ workableStatus: string;
43
+ headroomStatus: string;
44
+ underpoweredAdvice: string;
45
+ workableAdvice: string;
46
+ headroomAdvice: string;
47
+ sourceNote: string;
48
+ modelSourceNote: string;
49
+ manufacturerNote: string;
50
+ modelNote: string;
51
+ safetyNote: string;
52
+ thrustAxisLabel: string;
53
+ weightAxisLabel: string;
54
+ clearBenchData: string;
55
+ }